🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# zend执行过程 ## EG变量 executor_globals是一个全局变量,存储着许多信息(当前上下文、符号表、函数/类/常量表、堆栈等),EG宏就是用于访问executor_globals的某个成员. ```c //zend_global.h/_zend_executor_globals struct _zend_executor_globals { ... zend_array symbol_table; /* PHP全局变量表:$_GET,$_POST等 main symbol table */ HashTable included_files; /* 已经引入的脚本 files already included */ JMP_BUF *bailout; /* try-catch保存的catch跳转位置 */ int error_reporting; int exit_status; HashTable *function_table; /* 全部已编译的function哈希表,包括内部函数,用户自定义函数,函数调用将从这里查找 function symbol table */ HashTable *class_table; /* 全部已编译的class哈希表,new class 时从此查找 class table */ HashTable *zend_constants; /* 常量符号表 constants table */ zval *vm_stack_top;//栈内存池剩余可用内存起始位置 zval *vm_stack_end;//栈内存池结束位置 zend_vm_stack vm_stack; /* 运行栈内存池,一块空白的内存,用于分配PHP执行期间的一些数据结构(zend_execute),局部变量从这里分配 */ struct _zend_execute_data *current_execute_data; /* 指向当前正在执行的运行栈,函数调用就是分配一个新的zend_execute_data,然后将EG(current_execute_data)指向新的结构继续执行,调用完毕再还原回去,类似汇编call,ret指令的作用 */ zend_class_entry *fake_scope; /* used to avoid checks accessing properties */ ... HashTable *in_autoload; /* 在类的自动加载过程中会使用到 */ zend_function *autoload_func; /* 自动加载回调函数:__autoload() */ zend_bool full_tables_cleanup; ... HashTable regular_list; HashTable persistent_list; /* 持久化符号表,request请求结束后不释放可以跨request共享,在php_module_shutdown()阶段清理,*/ ... }; //zend/zend_globals_macros.h # define EG(v) (executor_globals.v) ``` # zend_compile.h/_zend_execute_data zend_execute_data是执行过程中最核心的一个结构,每次函数的调用、include/require、eval等都会生成一个新的结构,它表示当前的作用域、代码的执行位置以及局部变量的分配等等,等同于机器码执行过程中stack的角色. ```c //64位机器上,占80个字节 struct _zend_execute_data { const zend_op *opline;/* 指向当前执行的opcode,初始时指向zend_op_array起始位置executed opline */ zend_execute_data *call;/* 当前正在调用的子函数 current call */ zval *return_value;//返回值指针 zend_function *func;/* 当前执行的函数自身(非函数调用时为空)executed function */ zval This;/* 这个值并不仅仅是面向对象的this,还有另外两个值也通过这个记录:call_info + num_args,分别存在zval.u1.reserved、zval.u2.num_args */ zend_execute_data *prev_execute_data;//调用时指向父级调用位置作用空间 zend_array *symbol_table;//全局变量符号表 #if ZEND_EX_USE_RUN_TIME_CACHE void **run_time_cache;/* cache op_array->run_time_cache */ #endif #if ZEND_EX_USE_LITERALS zval *literals;/* 字面量数组,与func.op_array->literals相同 //cache op_array->literals*/ #endif }; ``` ![op_array与execute_data关系](https://box.kancloud.cn/005c7ed47923adba803d0f313417ad5f_450x676.png) ### 运行栈帧布局: #### linux程序运行内存分配 ![linux程序运行内存分配图](https://box.kancloud.cn/54c6b45c720066c49af3c93e125832b5_517x366.jpg) #### C程序运行栈 ![C程序运行栈](https://box.kancloud.cn/765da843dfd4eba7b53ad9301fc8adaa_328x492.png) ### ZEND分配运行栈 ```c //Zend/zend_execute.c : 2094 /* * 栈帧布局,所有运行栈空间是同时分配的 / Stack Frame Layout (the whole stack frame is allocated at once) * ================== * * +=========================================================+ * EG(current_execute_data) -> | zend_execute_data | * +---------------------------------------------------------+ * EX_CV_NUM(0) ---------> | VAR[0] = ARG[1] | 参数(arguments) * | ... | * | VAR[op_array->num_args-1] = ARG[N] | * | VAR[num_args] = CV[num_args] | 局部变量(remaining CVs) * | ... | * | VAR[op_array->last_var-1] = CV[last_var-1] | * | VAR[op_array->last_var] = TMP[0] | 临时变量(TMP/VARs) * | ... | * | VAR[op_array->last_var+op_array->T-1] = TMP[T] | * | ARG[N+1] (extra_args) | 其余参数(extra arguments) * | ... | * +---------------------------------------------------------+ */ ``` #### 变量类型: ```c // Zend/zend_compile.h #define IS_CONST (1<<0) //1 #define IS_TMP_VAR (1<<1) //2 #define IS_VAR (1<<2) //4 #define IS_UNUSED (1<<3) //8 #define IS_CV (1<<4) //16 ``` * IS_CONST:字面量,编译时就可确定且不会改变的值,比如:$a = "hello~",其中字符串"hello~"就是常量 * IS_TMP_VAR:临时变量,比如:$a = "hello~" . time(),其中"hello~" . time()的值类型就是IS_TMP_VAR,再比如:$a = "123" + $b,"123" + $b的结果类型也是IS_TMP_VAR,从这两个例子可以猜测,临时变量多是执行期间其它类型组合现生成的一个中间值,由于它是现生成的,所以把IS_TMP_VAR赋值给IS_CV变量时不会增加其引用计数 * IS_VAR:PHP变量,这个很容易认为是PHP脚本里的变量,其实不是,这里PHP变量的含义可以这样理解:PHP变量是没有显式的在PHP脚本中定义的,不是直接在代码通过$var_name定义的.这个类型最常见的例子是PHP函数的返回值,再如$a[0]数组这种,它取出的值也是IS_VAR,再比如$$a这种 * IS_UNUSED:表示操作数没有用 * IS_CV:PHP脚本变量,即脚本里通过$var_name定义的变量,这些变量是编译阶段确定的,所以是compile variable, 除了分配execute_data的存储空间外,还分配了CV(compiled variable,即PHP变量)、TMP_VAR(临时变量,例如执行if (!$a) echo 'a';,就需要一个临时变量来存储!$a的结果)的存储空间. EX宏是用于访问execute_data的成员 堆栈的结构体,堆栈的设计跟PHP 5类似: struct _zend_vm_stack { zval *top; /* 指向堆栈的顶端 */ zval *end; /* 指向堆栈的底端 */ zend_vm_stack prev; /* 指向上一个堆栈,当前堆栈剩余空间不足时,会向内存管理器申请新的内存创建新的堆栈 */ }; #### opcode执行时使用的寄存器优化 ```c define ZEND_VM_FP_GLOBAL_REG "%r14" //栈帧指针(frame pointer)寄存器 define ZEND_VM_IP_GLOBAL_REG "%r15" //指令指针寄存器 register const zend_op* volatile opline __asm__(ZEND_VM_IP_GLOBAL_REG); register zend_execute_data* volatile execute_data __asm__(ZEND_VM_FP_GLOBAL_REG); ``` ## 参考资料: https://yangxikun.github.io/php/2016/11/04/php-7-func-call.html https://github.com/pangudashu/php7-internal/ https://nikic.github.io/2017/04/14/PHP-7-Virtual-machine.html