@@ -20,12 +20,12 @@ ZEND_API void execute_ex(zend_execute_data *ex)
2020```
2121执行器实际是一个大循环,从第一条opcode开始执行,execute_data->opline指向当前执行的指令,执行完以后指向下一条指令,opline类似eip(或rip)寄存器的作用。通过这个循环,ZendVM完成opcode指令的执行。opcode执行完后以后指向下一条指令的操作是在当前handler中完成,也就是说每条执行执行完以后会主动更新opline,这里会有下面几个不同的动作:
2222```c
23- ZEND_VM_CONTINUE()
24- ZEND_VM_ENTER()
25- ZEND_VM_LEAVE()
26- ZEND_VM_RETURN()
23+ #define ZEND_VM_CONTINUE() return 0
24+ #define ZEND_VM_ENTER() return 1
25+ #define ZEND_VM_LEAVE() return 2
26+ #define ZEND_VM_RETURN() return -1
2727```
28- ZEND_VM_CONTINUE()表示继续执行下一条opcode;ZEND_VM_ENTER()/ZEND_VM_LEAVE()是调用函数时的动作,普通模式下ZEND_VM_ENTER()实际就是return 1,然后execute_ex()中会将execute_data切换到被调函数的结构上,对应的,在函数调用完成后ZEND_VM_LEAVE()会return 2,再将execute_data切换至原来的结构;ZEND_VM_RETURN()表示执行完成,比如exit,这时候execute_ex()将退出执行。下面看一个具体的例子:
28+ ZEND_VM_CONTINUE()表示继续执行下一条opcode;ZEND_VM_ENTER()/ZEND_VM_LEAVE()是调用函数时的动作,普通模式下ZEND_VM_ENTER()实际就是return 1,然后execute_ex()中会将execute_data切换到被调函数的结构上,对应的,在函数调用完成后ZEND_VM_LEAVE()会return 2,再将execute_data切换至原来的结构;ZEND_VM_RETURN()表示执行完成,返回-1给execute_ex(), 比如exit,这时候execute_ex()将退出执行。下面看一个具体的例子:
2929``` php
3030$a = "hi~";
3131echo $a;
@@ -111,4 +111,61 @@ r14 0x601010 6295568
111111(gdb) p (( zend_execute_data * )$r14 )-> ip
112112$3 = 9999
113113```
114+ 了解完全局寄存器变量,接下来我们再回头看下PHP7 中的用法,处理也比较简单,就是在execute_ex()执行各opcode指令的过程中,不再将execute_data作为参数传给handler,而是通过寄存器保存execute_data及opline的地址,handler使用时直接从全局变量(寄存器)读取,执行完再把下一条指令更新到全局变量。
114115
116+ 该功能需要GCC 4 .8 +支持,默认开启,可以通过 --disable-gcc-global-regs 编译参数关闭。以x86 _64 为例,execute_data使用r14 寄存器,opline使用r15 寄存器:
117+ ```c
118+ // file: zend_execute.c line: 2631
119+ # define ZEND_VM_FP_GLOBAL_REG "%r14 "
120+ # define ZEND_VM_IP_GLOBAL_REG "%r15 "
121+
122+ // file: zend_vm_execute.h line: 315
123+ register zend_execute_data* volatile execute_data __asm__(ZEND_VM_FP_GLOBAL_REG);
124+ register const zend_op* volatile opline __asm__(ZEND_VM_IP_GLOBAL_REG);
125+ ```
126+ execute_data、opline定义为全局变量,下面看下execute_ex()的变化,展开后:
127+ ```c
128+ ZEND_API void execute_ex(zend_execute_data *ex)
129+ {
130+ const zend_op *orig_opline = opline;
131+ zend_execute_data *orig_execute_data = execute_data;
132+
133+ //将当前execute_data、opline保存到全局变量
134+ execute_data = ex;
135+ opline = execute_data->opline
136+
137+ while (1 ) {
138+ ((opcode_handler_t)opline->handler)();
139+
140+ if (UNEXPECTED(!opline)) {
141+ execute_data = orig_execute_data;
142+ opline = orig_opline;
143+
144+ return ;
145+ }
146+ }
147+ }
148+ ```
149+ 这个时候调用各opcode指令的handler时就不再传入execute_data的参数了,handler使用时直接从全局变量读取,仍以上面的赋值ZEND_ASSIGN指令为例,handler展开后:
150+ ``` c
151+ static int __attribute__ ((fastcall)) ZEND_ASSIGN_SPEC_CV_CONST_HANDLER(void)
152+ {
153+ ...
154+
155+ //ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION()
156+ opline = execute_data->opline + 1;
157+ return;
158+ }
159+ ```
160+ 当调用函数时,会把execute_data、opline更新为被调函数的,然后回到execute_ex()开始执行被调函数的指令:
161+ ```c
162+ # define ZEND_VM_ENTER() execute_data = EG(current_execute_data); LOAD_OPLINE(); ZEND_VM_CONTINUE()
163+ ```
164+ 展开后:
165+ ``` c
166+ // ZEND_VM_ENTER()
167+ execute_data = execute_data->current_execute_data;
168+ opline = execute_data->opline;
169+ return ;
170+ ```
171+ 这两种处理方式并没有本质上的差异,只是通过全局寄存器变量提升了一些性能。
0 commit comments