入门客AI创业平台(我带你入门,你带我飞行)
博文笔记

zend_execute中op的执行

创建时间:2016-01-12 投稿人: 浏览次数:350

解释器引擎最终执行op的函数是zend_execute,实际上zend_execute是一个函数指针,在引擎初始化的时候zend_execute默认指向了execute,这个execute定义在{PHPSRC}/Zend/zend_vm_execute.h:

 

  1. ZEND_API void execute(zend_op_array *op_array TSRMLS_DC)  
  2. {  
  3.     zend_execute_data *execute_data;  
  4.     zend_bool nested = 0;  
  5.     zend_bool original_in_execution = EG(in_execution);  
  6.   
  7.   
  8.     if (EG(exception)) {  
  9.         return;  
  10.     }  
  11.   
  12.     EG(in_execution) = 1;  
  13.   
  14. zend_vm_enter:  
  15.     /* Initialize execute_data */  
  16.     execute_data = (zend_execute_data *)zend_vm_stack_alloc(  
  17.         ZEND_MM_ALIGNED_SIZE(sizeof(zend_execute_data)) +  
  18.         ZEND_MM_ALIGNED_SIZE(sizeof(zval**) * op_array->last_var * (EG(active_symbol_table) ? 1 : 2)) +  
  19.         ZEND_MM_ALIGNED_SIZE(sizeof(temp_variable)) * op_array->T TSRMLS_CC);  
  20.   
  21.     EX(CVs) = (zval***)((char*)execute_data + ZEND_MM_ALIGNED_SIZE(sizeof(zend_execute_data)));  
  22.     memset(EX(CVs), 0, sizeof(zval**) * op_array->last_var);  
  23.     EX(Ts) = (temp_variable *)(((char*)EX(CVs)) + ZEND_MM_ALIGNED_SIZE(sizeof(zval**) * op_array->last_var * (EG(active_symbol_table) ? 1 : 2)));  
  24.     EX(fbc) = NULL;  
  25.     EX(called_scope) = NULL;  
  26.     EX(object) = NULL;  
  27.     EX(old_error_reporting) = NULL;  
  28.     EX(op_array) = op_array;  
  29.     EX(symbol_table) = EG(active_symbol_table);  
  30.     EX(prev_execute_data) = EG(current_execute_data);  
  31.     EG(current_execute_data) = execute_data;  
  32.     EX(nested) = nested;  
  33.     nested = 1;  
  34.   
  35.     if (op_array->start_op) {  
  36.         ZEND_VM_SET_OPCODE(op_array->start_op);  
  37.     } else {  
  38.         ZEND_VM_SET_OPCODE(op_array->opcodes);  
  39.     }  
  40.   
  41.     if (op_array->this_var != -1 && EG(This)) {  
  42.         Z_ADDREF_P(EG(This)); /* For $this pointer */  
  43.         if (!EG(active_symbol_table)) {  
  44.             EX(CVs)[op_array->this_var] = (zval**)EX(CVs) + (op_array->last_var + op_array->this_var);  
  45.             *EX(CVs)[op_array->this_var] = EG(This);  
  46.         } else {  
  47.             if (zend_hash_add(EG(active_symbol_table), "this", sizeof("this"), &EG(This), sizeof(zval *), (void**)&EX(CVs)[op_array->this_var])==FAILURE) {  
  48.                 Z_DELREF_P(EG(This));  
  49.             }  
  50.         }  
  51.     }  
  52.   
  53.     EG(opline_ptr) = &EX(opline);  
  54.   
  55.     EX(function_state).function = (zend_function *) op_array;  
  56.     EX(function_state).arguments = NULL;  
  57.       
  58.     while (1) {  
  59.         int ret;  
  60. #ifdef ZEND_WIN32  
  61.         if (EG(timed_out)) {  
  62.             zend_timeout(0);  
  63.         }  
  64. #endif  
  65.   
  66.         if ((ret = EX(opline)->handler(execute_data TSRMLS_CC)) > 0) {  
  67.             switch (ret) {  
  68.                 case 1:  
  69.                     EG(in_execution) = original_in_execution;  
  70.                     return;  
  71.                 case 2:  
  72.                     op_array = EG(active_op_array);  
  73.                     goto zend_vm_enter;  
  74.                 case 3:  
  75.                     execute_data = EG(current_execute_data);  
  76.                 default:  
  77.                     break;  
  78.             }  
  79.         }  
  80.   
  81.     }  
  82.     zend_error_noreturn(E_ERROR, "Arrived at end of main loop which shouldn"t happen");  
  83. }  

 

此函数的参数为op_array,这是一个指向zend_op_array的指针,op_array是在编译过程中生成,这里有必要介绍一下zend_op_array这个类型.

 

 

zend_op_array简介

此类型的定义在{PHPSRC}/Zend/zend_compile.h:

  1. struct _zend_op_array {  
  2.     /* Common elements */  
  3.     zend_uchar type;  
  4.     char *function_name;          
  5.     zend_class_entry *scope;  
  6.     zend_uint fn_flags;  
  7.     union _zend_function *prototype;  
  8.     zend_uint num_args;  
  9.     zend_uint required_num_args;  
  10.     zend_arg_info *arg_info;  
  11.     zend_bool pass_rest_by_reference;  
  12.     unsigned char return_reference;  
  13.     /* END of common elements */  
  14.   
  15.     zend_bool done_pass_two;  
  16.   
  17.     zend_uint *refcount;  
  18.   
  19.     zend_op *opcodes;  
  20.     zend_uint last, size;  
  21.   
  22.     zend_compiled_variable *vars;  
  23.     int last_var, size_var;  
  24.   
  25.     zend_uint T;  
  26.   
  27.     zend_brk_cont_element *brk_cont_array;  
  28.     int last_brk_cont;  
  29.     int current_brk_cont;  
  30.   
  31.     zend_try_catch_element *try_catch_array;  
  32.     int last_try_catch;  
  33.   
  34.     /* static variables support */  
  35.     HashTable *static_variables;  
  36.   
  37.     zend_op *start_op;  
  38.     int backpatch_count;  
  39.   
  40.     zend_uint this_var;  
  41.   
  42.     char *filename;  
  43.     zend_uint line_start;  
  44.     zend_uint line_end;  
  45.     char *doc_comment;  
  46.     zend_uint doc_comment_len;  
  47.     zend_uint early_binding; /* the linked list of delayed declarations */  
  48.   
  49.     void *reserved[ZEND_MAX_RESERVED_RESOURCES];  
  50. };  
  51.   
  52. typedef struct _zend_op_array zend_op_array;  

 

此结构比较复杂,我们目前只介绍最基本的几个字段。

1.type:

    op_array的类型,首先需要说明的是,一段PHP代码被编译之后,虽然返回的是一个zend_op_array指针,但是实际上生成的zend_op_array结构可能不止一个,通过这个结构中的一些字段,例如function_name ,num_args等你也许会发现这个zend_op_array结构似乎能和函数产生一定的联系,确实如此,用户自定义的函数,以及用户定义的类的方法,都是一个zend_op_array结构,这些zend_op_array结构在编译过程中被保存在某些地方,例如用户自定义的函数被保存进了GLOBAL_FUNCTION_TABLE,这个是全局函数符号表,通过函数名可以在此表中检索到函数体。那么编译后返回的那个zend_op_array指针是什么呢,其实编译后返回的zend_op_array是执行的一个入口,也可以认为它是最外层,即不在任何函数体内的全局代码组成的op_array。然而全局代码,用户自定义函数,用户自定义的方法都拥有相同的type值:2 ,type可取值的宏定义为:

  1. #define ZEND_INTERNAL_FUNCTION              1  
  2. #define ZEND_USER_FUNCTION                  2  
  3. #define ZEND_OVERLOADED_FUNCTION            3  
  4. #define ZEND_EVAL_CODE                      4  
  5. #define ZEND_OVERLOADED_FUNCTION_TEMPORARY  5  

 

可以看到全局代码,用户函数,用户方法都对应的是ZEND_USER_FUNCTION,这个也是最常见的type了,其中ZEND_EVAL_CODE对应的是eval函数中的PHP代码,所以我们可以想到,eval函数参数中的PHP代码也会被编译成单独的zend_op_array。

 

2.function_name

    如果op_array是由用户定义的函数或则方法编译而生成,那么此字段对应函数的名字,如果是全局代码或则是eval部分的代码,那么此字段为控制

 

3.opcodes

    这个字段类型为zend_op *,因此这是一个zend_op的数组,这个数组保存的就是此编译过程中生成的op,如果不了解zend_op,可以看看之前的文章 OPcode简介 , 这个字段是最重要的部分了,zend_execute最终就是执行这里保存的op。

 

现在基本对参数op_array有了一定的了解,那么我们就开始进入到execute中

 

执行过程详解

  execute函数开始的时候是一些基础变量的申明,其中zend_execute_data *execute_data;是执行期的数据结构,此变量在进行一定的初始化之后将会被传递给每个op的handler函数作为参数,op在执行过程中随时有可能改变execute_data中的内容。

  第14行zend_vm_enter 这个跳转标签是作为虚拟机执行的入口,当op中涉及到函数调用的时候,就有可能会跳转到这里来执行函数体。

  第16行到第19行为execute_data分配空间

  第21行到第32行主要是对execute_data进行一些初始化,以及保存现场工作,要保存现场是因为在进入函数调用的时候,需要保存当前一些运行期间的数据,在函数调用结束之后再进行还原,可以想象为操作系统中进程调度,当进程在调出的时候需要保存寄存器等上下文环境,而当进程被调入的时候再取出来继续执行。

  第41行到第51行主要是在当前动态符号表中加入$this变量,这个是在调用对象的方法时才有必要进行。

  第58行开始的while无限循环就是开始执行op_array中的opcodes了,在第66行中调用当前执行的op的handler:

EX(opline)->handler(execute_data TSRMLS_CC))

  然后如果handler的返回值小于0则循环继续,如果大于0则进入一个switch结构:

 

当返回值为1时:

    execute函数将返回,执行也就结束了

当返回值为2时:

    op_array被重新设置,并跳转到zend_vm_enter ,这个一般是函数调用或则执行eval函数中的代码,将在新的上下文执行相关函数的op_array

当返回值为3时: 

    循环体继续继续执行,当然再继续执行之前,EX(opline)已经往后移了一位(可能多位),也就是已经指向了后面一个新的opline,于是继续执行新的opline

当返回其他值时:

    结束循环,报错,结束应该用return,也就是返回1

 

在op的handler中返回特定的值都被定义成了宏,例如{PHPSRC}/Zend/zend_execute.c中定义的:

  1. #define ZEND_VM_NEXT_OPCODE() /  
  2.     CHECK_SYMBOL_TABLES() /  
  3.     EX(opline)++; /  
  4.     ZEND_VM_CONTINUE()  
  5.   
  6. #define ZEND_VM_SET_OPCODE(new_op) /  
  7.     CHECK_SYMBOL_TABLES() /  
  8.     EX(opline) = new_op  
  9.   
  10. #define ZEND_VM_JMP(new_op) /  
  11.     CHECK_SYMBOL_TABLES() /  
  12.     if (EXPECTED(!EG(exception))) { /  
  13.         EX(opline) = new_op; /  
  14.     } /  
  15.     ZEND_VM_CONTINUE()  
  16.   
  17. #define ZEND_VM_INC_OPCODE() /  
  18.     EX(opline)++  

以及在{PHPSRC}/Zend/zend_vm_execute.c中定义的:

  1. #define ZEND_VM_CONTINUE()   return 0  
  2. #define ZEND_VM_RETURN()     return 1  
  3. #define ZEND_VM_ENTER()      return 2  
  4. #define ZEND_VM_LEAVE()      return 3  
  5. #define ZEND_VM_DISPATCH(opcode, opline) return zend_vm_get_opcode_handler(opcode, opline)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);  

 

简单介绍功能

ZEND_VM_NEXT_OPCODE():

    移动到下一条op,返回0,不进入switch,循环继续(这个是最常用到的)

ZEND_VM_SET_OPCODE(new_op):

    当前opline设置成new_op

ZEND_VM_JMP(new_op) :

    当前opline设置成new_op,返回0,不进入switch,循环继续

ZEND_VM_INC_OPCODE():

    仅仅移动到下一条op

 

 

执行环境的切换

   在前面的内容已经提到,用户自定义函数,类方法,eval的代码都会编译成单独的op_array,那么当进行函数调用等操作时,必然涉及到调用前的op_array执行环境和新的函数的op_array执行环境的切换,这一段我们将以调用用户自定义函数来介绍整个切换过程如何进行。

  介绍此过程前必须了解执行环境的相关数据结构,涉及到执行环境的数据结构主要有两个:

1.执行期全局变量结构

   相关的定义在{PHPSRC}/Zend/zend_globals_macros.h:

 

  1. /* Executor */  
  2. #ifdef ZTS  
  3. # define EG(v) TSRMG(executor_globals_id, zend_executor_globals *, v)  
  4. #else  
  5. # define EG(v) (executor_globals.v)  
  6. extern ZEND_API zend_executor_globals executor_globals;  
  7. #endif  

 

这里是一个条件编译,ZTS表示线程安全启用,为了简化,我们这里以非线程安全模式的情况下来介绍,那么执行期的全局变量就是executor_globals,其类型为zend_executor_globals, zend_executor_globals的定义在{PHPSRC}/Zend/zend_globals.h,结构比较庞大,这里包含了整个执行期需要用到的各种变量,无论是哪个op_array在执行,都共用这一个全局变量,在执行过程中,此结构中的一些成员可能会改变,比如当前执行的op_array字段active_op_array,动态符号表字段active_symbol_table可能会根据不同的op_array而改变,This指针会根据在不同的对象环境而改变。

另外还定义了一个EG宏来取此变量中的字段值,此宏是针对线程安全和非线程安全模式的一个封装。

 

2.每个op_array自身的执行数据

   针对每一个op_array,都会有自己执行期的一些数据,在函数execute开始的时候我们能看到zend_vm_enter跳转标签下面就会初始一个局部变量execute_data,所以我们每次切换到新的op_array的时候,都会为新的op_array建立一个execute_data变量,此变量的类型为zend_execute_data的指针,相关定义在{PHPSRC}/Zend/zend_compile.h:

  

  1. struct _zend_execute_data {  
  2.     struct _zend_op *opline;  
  3.     zend_function_state function_state;  
  4.     zend_fun
声明:该文观点仅代表作者本人,入门客AI创业平台信息发布平台仅提供信息存储空间服务,如有疑问请联系rumenke@qq.com。