Skip to content

Commit 8b3d976

Browse files
committed
update
1 parent ddfbddd commit 8b3d976

File tree

1 file changed

+62
-5
lines changed

1 file changed

+62
-5
lines changed

3/zend_global_register.md

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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~";
3131
echo $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

Comments
 (0)