@@ -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