Skip to content

Commit 6cf83f6

Browse files
committed
update
1 parent 988315e commit 6cf83f6

File tree

4 files changed

+34
-20
lines changed

4 files changed

+34
-20
lines changed

php_impl/variable.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
## 4.1 变量
22

3-
### 4.1.1 变量的定义、赋值
3+
### 4.1.1 变量的赋值、销毁
4+
PHP变量定义:变量名 = 变量值,其中变量名有两种类型:CV($var_name)、VAR($$var_name),这里的CV、VAR等就是对应前面Zend编译过程中提到的那5种[__操作数类型__](../zend_compile.md#操作数类型)。
5+
6+
普通PHP变量赋值操作对应的opcode是`ZEND_ASSIGN`,
47

58
### 4.1.2 变量类型转换
69

php_language.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# PHP语法实现
2-
这一章是根据PHP语法的实现详细解析Zend内核的处理过程,比如变量的赋值、函数的调用……有些内容前会跟之前部分章节内容重复,这里将再从具体的操作总结性的分析下。
2+
写在前面:
3+
4+
这一章是根据PHP语法的实现详细解析Zend内核的处理过程,即各个opcode的handler处理,比如变量的赋值、函数的调用……有些内容前会跟之前部分章节内容重复,这里将再从具体的操作总结性的分析下,相比《第3章 Zend虚拟机》的内容,这一章分析的内容更加具体,虽然可能没有太大意义,但可以帮助我们更好的了解PHP的实现。
5+
6+
本章内容主要涉及zend_vm_def.h、zend_vm_execute.h,其中zend_vm_execute.h是根据zend_vm_def.h的定义通过zend_vm_gen.php脚本生成的,所以开发者实际编写opcode的handler是在zend_vm_def.h中,但是为了更加直观的分析各handler的处理,我们将重点分析生成的zend_vm_execute.h,同时只分析常见的用法,也不会把所有情况统统分析一遍。
7+
8+
目录:
39

410
## 1.变量
511
### 1.1 变量的定义、赋值
@@ -24,8 +30,9 @@
2430
## 5.跳转语句
2531

2632
## 6.函数
27-
### 6.1 函数传参
28-
### 6.2 函数返回值
33+
### 6.1 函数定义
34+
### 6.2 函数调用
35+
### 6.3 函数返回值
2936

3037
## 7.文件加载
3138

zend_compile.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
# PHP代码的编译
1+
## 3.1 PHP代码的编译
22

33
PHP是解析型高级语言,事实上从Zend内核的角度来看PHP就是一个普通的C程序,它有main函数,我们写的PHP代码是这个程序的输入,然后经过内核的处理输出结果,内核将PHP代码"翻译"为C程序可识别的过程就是PHP的编译。
44

55
那么这个"翻译"过程具体都有哪些操作呢?
66

7+
### 3.1.1 PHP的基本编译实现
8+
79
C程序在编译时将一行行代码编译为机器码,每一个操作都认为是一条机器指令,这些指令写入到编译后的二进制程序中,执行的时候将二进制程序load进相应的内存区域(常量区、数据区、代码区)、分配运行栈,然后从代码区起始位置开始执行,这是C程序编译、执行的简单过程。
810

911
同样,PHP的编译与普通的C程序类似,只是PHP代码没有编译成机器码,而是解析成了若干条opcode数组,每条opcode就是C里面普通的struct,含义对应C程序的机器指令,执行的过程就是引擎依次执行opcode,比如我们在PHP里定义一个变量:`$a = 123;`,最终到内核里执行就是malloc一块内存,然后把值写进去。
@@ -64,7 +66,9 @@ err:
6466
6567
![zend_compile](img/zend_compile.png)
6668
67-
最终生成的opcode数组结构为:
69+
### 3.1.2 编译输出
70+
71+
PHP编译最终生成的opcode数组结构为:
6872
6973
```c
7074
truct _zend_op_array {
@@ -131,7 +135,7 @@ struct _zend_op {
131135

132136
opcode各字段含义下面展开说明。
133137

134-
### handler
138+
#### 3.1.2.1 handler
135139
handler为每条opcode对应的C语言编写的__处理过程__,所有opcode对应的处理过程定义在`zend_vm_def.h`中,值得注意的是这个文件并不是编译时用到的,因为opcode的__处理过程__有三种不同的提供形式:CALL、SWITCH、GOTO,默认方式为CALL,这个是什么意思呢?
136140

137141
每个opcode都代表了一些特定的处理操作,这个东西怎么提供呢?一种是把每种opcode负责的工作封装成一个function,然后执行器循环执行即可,这就是CALL模式的工作方式;另外一种是把所有opcode的处理方式通过C语言里面的label标签区分开,然后执行器执行的时候goto到相应的位置处理,这就是GOTO模式的工作方式;最后还有一种方式是把所有的处理方式写到一个switch下,然后通过case不同的opcode执行具体的操作,这就是SWITCH模式的工作方式。
@@ -254,13 +258,13 @@ ZEND_API void zend_vm_set_opcode_handler(zend_op* op)
254258
#define _CV_CODE 4
255259
```
256260
257-
### 操作数
261+
#### 3.1.2.2 操作数
258262
259263
每条opcode都有两个操作数(不一定都有用)、一个返回值,其中handler是具体执行的方法,handler通过opcode、op1_type、op2_type三个值索引到,每条opcode都最多有(5*5)个不同的处理handler,后面分析zend执行的时候再具体讲这块。
260264
261265
操作数记录着当前指令的关键信息,可以用于变量的存储、访问,比如赋值语句:"$a = 45;",两个操作数分别记录"$a"、"45"的存储位置,执行时根据op2取到值"45",然后赋值给"$a",而"$a"的位置通过op1获取到。当然操作数并不是全部这么用的,上面只是赋值时候的情况,其它操作会有不同的用法,如函数调用时的传参,op1记录的就是传递的参数是第几个,op2记录的是参数的存储位置,result记录的是函数接收参数的存储位置。
262266
263-
### 操作数类型
267+
#### 3.1.2.3 操作数类型
264268
265269
每个操作都有5种不同的类型:
266270
@@ -273,13 +277,13 @@ ZEND_API void zend_vm_set_opcode_handler(zend_op* op)
273277
```
274278
* IS_CONST 常量(字面量),编译时就可确定且不会改变的值,比如:$a = "hello~",其中字符串"hello~"就是常量
275279
* IS_TMP_VAR 临时变量,比如:$a = "hello~" . time(),其中`"hello~" . time()`的值类型就是IS_TMP_VAR,再比如:$a = "123" + $b,`"123" + $b`的结果类型也是IS_TMP_VAR,从这两个例子可以猜测,临时变量多是执行期间其它类型组合现生成的一个中间值,由于它是现生成的,所以把IS_TMP_VAR赋值给IS_CV变量时不会增加其引用计数
276-
* IS_VAR PHP变量,这个很容易认为是PHP脚本里的变量,其实不是,这里PHP变量的含义这样理解:PHP变量是没有变量名的,不是直接在代码通过`$var_name`定义的,IS_CV则有变量名。这个类型最常见的例子是PHP函数的返回值,再如`$a[0]`数组这种,它取出的值也是`IS_VAR`,再比如`$$a`这种
280+
* IS_VAR PHP变量,这个很容易认为是PHP脚本里的变量,其实不是,这里PHP变量的含义可以这样理解:PHP变量是没有显式的在PHP脚本中定义的,不是直接在代码通过`$var_name`定义的。这个类型最常见的例子是PHP函数的返回值,再如`$a[0]`数组这种,它取出的值也是`IS_VAR`,再比如`$$a`这种
277281
* IS_UNUSED 表示操作数没有用
278-
* IS_CV PHP变量,即脚本里通过`$var_name`定义的变量,这些变量是编译阶段确定的,所以是compile variable,
282+
* IS_CV PHP脚本变量,即脚本里通过`$var_name`定义的变量,这些变量是编译阶段确定的,所以是compile variable,
279283

280284
`result_type`除了上面几种类型外还有一种类型`EXT_TYPE_UNUSED (1<<5)`,返回值没有使用时会用到,这个跟`IS_UNUSED`的区别是:`IS_UNUSED`表示本操作返回值没有意义(也可简单的认为没有返回值),而`EXT_TYPE_UNUSED`的含义是有返回值,但是没有用到,比如函数返回值没有接收。
281285

282-
### 常量(字面量)、变量的读写
286+
#### 3.1.2.4 常量(字面量)、变量的读写
283287

284288
我们先想一下C程序是如何读写字面量、变量的。
285289

zend_executor.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
# Zend引擎执行过程
1+
## 3.3 Zend引擎执行过程
22
Zend引擎主要包含两个核心部分:编译、执行:
33

44
![zend_vm](img/zend_vm.png)
55

66
前面分析了Zend的编译过程以及PHP用户函数的实现,接下来分析下Zend引擎的执行过程。
77

8-
## 1、数据结构
8+
### 3.3.1 数据结构
99
执行流程中有几个重要的数据结构,先看下这几个结构。
1010

11-
### 1.1opcode
11+
#### 3.3.1.1 opcode
1212
opcode是将PHP代码编译产生的Zend虚拟机可识别的指令,php7共有173个opcode,定义在`zend_vm_opcodes.h`中,PHP中的所有语法实现都是由这些opcode组成的。
1313

1414
```c
@@ -26,7 +26,7 @@ struct _zend_op {
2626
};
2727
```
2828

29-
### 1.2zend_op_array
29+
#### 3.3.1.2 zend_op_array
3030
`zend_op_array`是Zend引擎执行阶段的输入,是opcode的集合(当然并不仅仅如此)。
3131

3232
![zend_op_array](img/zend_op_array.png)
@@ -81,7 +81,7 @@ truct _zend_op_array {
8181

8282
```
8383

84-
### 1.3zend_executor_globals
84+
#### 3.3.1.3 zend_executor_globals
8585
`zend_executor_globals executor_globals`是PHP整个生命周期中最主要的一个结构,是一个全局变量,在main执行前分配(非ZTS下),直到PHP退出,它记录着当前请求全部的信息,经常见到的一个宏`EG`操作的就是这个结构。
8686
```c
8787
//zend_compile.c
@@ -97,7 +97,7 @@ ZEND_API zend_executor_globals executor_globals;
9797
9898
![EG](img/EG.png)
9999
100-
### 1.4zend_execute_data
100+
#### 3.3.1.4 zend_execute_data
101101
`zend_execute_data`是执行过程中最核心的一个结构,每次函数的调用、include/require、eval等都会生成一个新的结构,它表示当前的作用域、代码的执行位置以及局部变量的分配等等,等同于机器码执行过程中stack的角色,后面分析具体执行流程的时候会详细分析其作用。
102102
103103
```c
@@ -122,7 +122,7 @@ struct _zend_execute_data {
122122
};
123123
```
124124

125-
## 2执行流程
125+
### 3.3.2 执行流程
126126
Zend的executor与linux二进制程序执行的过程是非常类似的,在C程序执行时有两个寄存器ebp、esp分别指向当前作用栈的栈顶、栈底,局部变量全部分配在当前栈,函数调用、返回通过`call`、`ret`指令完成,调用时`call`将当前执行位置压入栈中,返回时`ret`将之前执行位置出栈,跳回旧的位置继续执行,在Zend VM中`zend_execute_data`就扮演了这两个角色,`zend_execute_data.prev_execute_data`保存的是调用方的信息,实现了`call/ret`,`zend_execute_data`后面会分配额外的内存空间用于局部变量的存储,实现了`ebp/esp`的作用。
127127

128128
注意:在执行前分配内存时并不仅仅是分配了`zend_execute_data`大小的空间,除了`sizeof(zend_execute_data)`外还会额外申请一块空间,用于分配局部变量、临时(中间)变量等,具体的分配过程下面会讲到。
@@ -276,7 +276,7 @@ ZEND_API void execute_ex(zend_execute_data *ex)
276276
大概的执行过程上面已经介绍过了,这里只分析下整体执行流程,至于PHP各语法具体的handler处理后面会单独列一章详细分析。
277277

278278
#### (4)释放stack
279-
这一步就比较简单了,只是将申请的`zend_execute_data`内存释放给内存池,具体的操作只需要修改几个指针即可:
279+
这一步就比较简单了,只是将申请的`zend_execute_data`内存释放给内存池(注意这里并不是变量的销毁),具体的操作只需要修改几个指针即可:
280280

281281
```c
282282
static zend_always_inline void zend_vm_stack_free_call_frame_ex(uint32_t call_info, zend_execute_data *call)

0 commit comments

Comments
 (0)