前言
写了多年php,我们知道脚本语言很灵活,开发起来速度很快。但是否想过,它为什么会这样呢?到底在它在底层做了哪些工作?知其然要知其所以然,本文就来探讨一下php底层的一些实现机制。
我们知道,对于编程语言,我们可以将其分成编译型语言和解释型语言两种。
编译型语言
对于C语言,C++,编译成机器码(二进制)来运行。
Java语言,把.java 编译成.class, 称为bytecode(字节码),由jvm来运行。
解释型语言
解释器解释执行。 典型的如: linux shell。由解释器逐行来执行命令。那么,php到底是属于哪种呢?
PHP是先编译后执行
PHP稍有特殊,虽然是一个脚本语言,但不是靠解释器解释。而是zend虚拟机执行,屏蔽了操作系统的区别。PHP是一种适用于web开发的动态语言。具体点说,就是一个用C语言实现包含大量组件的软件框架。更狭义点看,可以把它认为是一个强大的UI框架。了解PHP底层实现的目的是什么?动态语言要像用好首先得了解它,内存管理、框架模型值得我们借鉴,通过扩展开发实现更多更强大的功能,优化我们程序的性能。
1. PHP的设计理念及特点
- 多进程模型:由于PHP是多进程模型,不同请求间互不干涉,这样保证了一个请求挂掉不会对全盘服务造成影响,当然,随着时代发展,PHP也早已支持多线程模型。
- 弱类型语言:和C/C++、Java、C#等语言不同,PHP是一门弱类型语言。一个变量的类型并不是一开始就确定不变,运行中才会确定并可能发生隐式或显式的类型转换,这种机制的灵活性在web开发中非常方便、高效,具体会在后面PHP变量中详述。
- 引擎(Zend)+组件(ext)的模式降低内部耦合。
- 中间层(sapi)隔绝web server和PHP。
- 语法简单灵活,没有太多规范。缺点导致风格混杂,但再差的程序员也不会写出太离谱危害全局的程序。
2. PHP的四层体系
PHP的核心架构如下图:

从图上可以看出,PHP从下到上是一个4层体系:
- Zend引擎:Zend整体用纯C实现,是PHP的内核部分,它将PHP代码翻译(词法、语法解析等一系列编译过程)为可执行opcode的处理并实现相应的处理方法、实现了基本的数据结构(如hashtable、oo)、内存分配及管理、提供了相应的api方法供外部调用,是一切的核心,所有的外围功能均围绕Zend实现。
- Extensions:围绕着Zend引擎,extensions通过组件式的方式提供各种基础服务,我们常见的各种内置函数(如array系列)、标准库等都是通过extension来实现,用户也可以根据需要实现自己的extension以达到功能扩展、性能优化等目的(如贴吧正在使用的PHP中间层、富文本解析就是extension的典型应用)。
- Sapi:Sapi全称是Server Application Programming Interface,也就是服务端应用编程接口,Sapi通过一系列钩子函数,使得PHP可以和外围交互数据,这是PHP非常优雅和成功的一个设计,通过sapi成功的将PHP本身和上层应用解耦隔离,PHP可以不再考虑如何针对不同应用进行兼容,而应用本身也可以针对自己的特点实现不同的处理方式。
- 上层应用:这就是我们平时编写的PHP程序,通过不同的sapi方式得到各种各样的应用模式,如通过webserver实现web应用、在命令行下以脚本方式运行等等。
如果PHP是一辆车,那么车的框架就是PHP本身,Zend是车的引擎(发动机),Ext下面的各种组件就是车的轮子,Sapi可以看做是公路,车可以跑在不同类型的公路上,而一次PHP程序的执行就是汽车跑在公路上。因此,我们需要:性能优异的引擎+合适的车轮+正确的跑道。
3. Sapi
如前所述,Sapi通过通过一系列的接口,使得外部应用可以和PHP交换数据并可以根据不同应用特点实现特定的处理方法,我们常见的一些sapi有:
- apache2handler:这是以apache作为webserver,采用mod_PHP模式运行时候的处理方式,也是现在应用最广泛的一种。
- cgi:这是webserver和PHP直接的另一种交互方式,也就是大名鼎鼎的fastcgi协议,在最近今年fastcgi+PHP得到越来越多的应用,也是异步webserver所唯一支持的方式。
- cli:命令行调用的应用模式
4. PHP的执行流程&opcode
我们先来看看PHP代码的执行所经过的流程。

从图上可以看到,PHP实现了一个典型的动态语言执行过程:拿到一段代码后,经过词法解析、语法解析等阶段后,源程序会被翻译成一个个指令(opcodes),然后ZEND虚拟机顺次执行这些指令完成操作。PHP本身是用C实现的,因此最终调用的也都是C的函数,实际上,我们可以把PHP看做是一个C开发的软件。
PHP的执行的核心是翻译出来的一条一条指令,也即opcode。Opcode是PHP程序执行的最基本单位。一个opcode由两个参数(op1,op2)、返回值和处理函数组成。PHP程序最终被翻译为一组opcode处理函数的顺序执行。
常见的几个处理函数:
1 2 3 4 5 6 |
ZEND_ASSIGN_SPEC_CV_CV_HANDLER : 变量分配 ($a=$b) ZEND_DO_FCALL_BY_NAME_SPEC_HANDLER:函数调用 ZEND_CONCAT_SPEC_CV_CV_HANDLER:字符串拼接 $a.$b ZEND_ADD_SPEC_CV_CONST_HANDLER: 加法运算 $a+2 ZEND_IS_EQUAL_SPEC_CV_CONST:判断相等 $a==1 ZEND_IS_IDENTICAL_SPEC_CV_CONST:判断相等 $a===1 |
1.变量的底层实现
PHP底层是C语言来实现的,C语言是强类型,而PHP是弱类型语言,是如何实现的?
PHP的源码包:
1 2 3 4 5 6 7 8 |
|__ ext |__ main |__ pear |__ sapi |__ tests |__ TSRM |__ Zend |__ .gdbinit |
最核心的是Zend,这是zend虚拟的实现。包括栈,数据类型,编译器等. 最重要的main,PHP的一些内建函数,最重要的函数都在该目录下.最大的一个目录 ext, PHP的扩展.
PHP的大部分功能,都是以extenstion形式来完成的。如果自身开发了一个扩展,也放入ext目录下。
弱类型语言变量的实现:
1 2 3 4 5 6 7 |
/* zend.h */ struct _zval_struct { zvalue_value value; /* 值 */ zend_uint refcount__gc; zend_uchar type; /* 活动类型 */ zend_uchar is_ref__gc; } |
PHP中的一个变量,zend虚拟机中,使用的是 _zval_struct 的结构体来描述,变量的值也是一个就结构体来描述.
_zval_struct的结构体是由 四个字段/域 (可以理解成关联数组)
zvalue_value value; /* 值 */
PHP变量的值,存储这个字段中。具体存储的位置:
1 2 3 4 5 6 7 8 9 |
/* value 值 是一个 联合 */ /* zend.h */ typedef union _zval_value { long lval; /* long value */ double dval; /* double value */ struct { char * val; int len; } str; HashTable *ht; /* hash table 指针 */ zend_object_value obj; } zvalue_value; |
Zend对变量的表示
zend实现了 zval结构体
1 2 3 4 5 |
{ value: [联合体] /* 联合体的内容可能是C语言中的long,double,hashtable(*ht),obj, 联合体只能是其中一种类型,是一个枚举 */ type: 变量类型 , /* IS_NULL,IS_BOOL,IS_STRING, IS_LONG,IS_DOUBLE,IS_ARRAY,IS_OBJECT,IS_RESOURCE */ refcount_gc is_ref_gc } |
C语言中类型对应PHP中的数据类型:
1 2 3 4 5 |
long -> int double -> double hashtable -> array struct -> string obj -> object |
例如:
1 2 3 4 5 6 7 8 9 10 |
$a = 3; { value: [long lval = 3] type: IS_LONG } $a = 3.5; { value: [double dval = 3.5] type: IS_DOUBLE } |
变量类型的实现
zend_uchar type; /* 活动类型 */
可以根据上下文环境来强制转换。
例如:需要echo 的时候 就转换成 string
需要加减运算就 转换成 int
PHP 中有8中数据类型,为什么zval->value 联合体中,只有5中 ?
1: NULL,直接 zval->type = IS_NULL, 就可以表示,不必设置 value 的值。
2:BOOL, zval->type = IS_BOOL. 再设置 zval.value.lval = 1/0; (C语言中没有布尔值,都是通过1,0,来表示)
3: resource ,资源型,往往是服务器上打开一个接口,如果 文件读取接口。 zval->type = IS_RESOURCE, zval->type.lval = 服务器上打开的接口编号。
1 2 3 4 |
struct { char * val; int len; } str; |
PHP中,字符串类型,长度是已经缓存的,调用strlen时,系统可以直接返回其长度,不需要计算。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$b = 'hello'; { union_zvalue { // 字符串的指针 struct{ char: 'hello'; len: 5 } str; } type: IS_STRING; refcount_gc: 1, is_ref_gc: 0 } |
//在PHP中字符串的长度,是直接体现在其结构体中,所以调用strlen(); 速度非常快,时间复杂度为0(1)
1 |
echo strlen($b); |
符号表
符号表symbol_table,变量的花名册
符号表是什么?
符号表示一张哈希表(哈希结构理解成关联数组)里面存储了变量名-> 变量zval结构体的地址
1 2 3 4 5 6 7 |
struct _zend_executor_globals { ... ... HashTable * active_symbol_table /* 活动符号表 */ HashTable symbol_table /* 全局符号表 */ HashTable included_files; /* files already included */ } |
// 变量花名册
1 2 3 |
$a = 3; $b = 1.223; $c = 'hello'; |
/** *
* 生成了3个结构体
* 同时,全局符号表,中多了三条记录
* a —> 0x123 —> 结构体 { 3 }
* b —> 0x21a —> 结构体 { 1.223 }
* c —> 0x1A0 —> 结构体 { hello }
*
*/
// 变量声明
// 第一:结构体生成
// 第二:符号表中多了记录,变量的花名册
// 第三:指向结构体
传值赋值
传值赋值发生了什么
在传值赋值时:
以:
$a = 3;
$b = $a;
为例:
并没有再次产生结构体,而是2个变量共用1个结构体
此时,2个变量,指向同1个结构体
refcount_gc 值为 2 (如果没有指针指引,会有垃圾回收机制清除)
写时复制
cow写时复制特性
1 2 3 4 5 6 7 8 9 10 |
$a = 3; $b = $a; /** * * 是否产生了2 个结构体? * 不是,共用1个, refcount_gc = 2; * */ $b = 5; echo$a, $b; // 3, 5 |
// $a,$b 指向同一个结构体,那么,修改$b或$a,对方会不会受干扰 ?
没有干扰到对方。具有写时复制的特性
如果有一方修改,将会造成结构体的分裂
结构体一开始共用,到某一方要修改值时,才分裂。这种特性称为:COW 。Copy On Write。
引用赋值
引用赋值发生了什么?
当引用赋值时,双方共用一个结构体(is_ref_gc=1)
关系图例展示:
强制分裂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
<?php // 强制分裂 $a = 3; /** * { * value: 3; * type: IS_LONG; * refcount_gc: 1; * is_ref_gc: 0; * } */$b = $a; /** * { * value: 3; * type: IS_LONG; * refcount_gc: 2; * is_ref_gc: 0; * } */ $c = &$a; // 不会按照 底下结构体变化 /** * { * value: 3; * type: IS_LONG; * refcount_gc: 3; * is_ref_gc: 1; * } */ // 正确的结构体变化 // 如果is_ref_gc 0->1 的过程中(从0到1,表示想引用变量)。refcount_gc>1。多个变量共享一个变量值。将会产生强制分裂 /** * // $a $c 结构体 * { * value: 3; * type: IS_LONG; * refcount_gc: 2; * is_ref_gc: 1; * } * * // $b 结构体 * { * value: 3; * type: IS_LONG; * refcount_gc: 1; * is_ref_gc: 0; * } * */ $c = 5; // a c /** * value: 5 * type: IS_LONG; * refcount_gc: 2; * is_ref_gc: 1; */ // b /** * value: 3 * type: IS_LONG; * refcount_gc: 1; * is_ref_gc: 0; */ echo$a, $b, $c; // 5 , 3 , 5 |
引用数组时的一些奇怪现象
1 2 3 4 5 6 |
// 引用数组时的怪现象 $arr = array(0, 1, 2, 3); $tmp = $arr; $arr[1] = 11; echo$arr[1]; // 1 // 数组不会比较细致的检查,多维数组存在。 因此,判断的时候,只会判断外面 一层的 结构体。 |
数组不会比较细致的检查
// 先 引用 后 赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$arr = array(0, 1, 2, 3); $x = &$arr[1]; $tmp = $arr; $arr[1] = 999; echo$tmp[1]; // 999 . hash表中的zvalue结构体中会变成引用类型。 // 只去关注外面一层结构体,而不去关注 hash表中的值。 echo'<br/>'; // 先赋值,后引用 $arr = array(0, 1, 2, 3); $tmp = $arr; $x = &$arr[1]; $arr[1] = 999; echo$tmp[1]; // 1 |
循环数组
循环数组时的怪现象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// 循环数组时的怪现象 $arr = array(0, 1, 2, 3); foreach ( $arras$v ) { } var_dump(current($arr)); // 数组指针停留在数组结尾处, 取不到值. false echo'<br/>'; $arr = array(0, 1, 2, 3); foreach ( $arras$val=>$key ) { // foreach 使用的 $arr 是 $arr的副本. $arr[$key] = $val; // 修改之后,就会产生分裂。 foreach 遍历的是 $arr 的副本。 但是原数组的指针已经走了一步. } var_dump(current($arr)); // 1 $arr = array('a', 'b', 'c', 'd'); foreach ( $arras &$val ) { // 该foreach 会导致 $val = &$arr[3]; } foreach ( $arras$val ) { print_r($arr); echo'<br/>'; } |
// 两个问题:
// 数组使用时,要慎用引用。
// foreach 使用后,不会把数组的内部指针重置, 使用数组时,不要假想内部指针指向数组头部. 也可以在foreach 之后 reset(); 指针。
符号表与作用域
当执行到函数时,会生成函数的“执行环境结构体”,包含函数名,参数,执行步骤,所在的类(如果是方法),以及为这个函数生成一个符号表。符号表统一放在栈上,并把active_symbol_table指向刚产生的符号表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// Zend/zend_compiles.h 文件中 // 源码: struct _zend_execute_data { struct _zend_op *opline; zend_function_state function_state; zend_op_array *op_array; zval *object; HashTable *symbol_table; struct _zend_execute_data *prev_execute_data; zval *old_error_reporting; zend_bool nested; zval **original_return_value; zend_class_entry *current_scope; zend_class_entry *current_called_scope; zval *current_this; struct _zend_op *fast_ret; /* used by FAST_CALL/FAST_RET (finally keyword) */ zval *delayed_exception; call_slot *call_slots; call_slot *call; }; |
1 2 3 4 5 6 7 8 9 10 11 |
// 简化: struct _zend_execute_data { ... zend_op_array *op_array; // 函数的执行步骤. 如果是函数调用。是函数调用的后的opcode HashTable *symbol_table; // 此函数的符号表地址 zend_class_entry *current_scope; // 执行当前作用域 zval * current_this; // 对象 调用 this绑定 zval * current_object; // object 的指向 ... } |
一个函数调用多次,会有多少个*op_array ?
一个函数产生 一个*op_array. 调用多次,会产生多个 环境结构体, 会依次入栈,然后顺序执行。
调用多少次,就会入栈多少次。不同的执行环境,靠 唯一的 *op_array 来执行。
函数什么时候调用, 函数编译后的 opcode 什么时候执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$age = 23; functiont() { $age = 3; echo$age; } t(); /** * t 函数 在执行时,根据函数的参数,局部变量等,生成一个执行环境结构体。 * 结构体 入栈,函数编译后的 opcode, 称为 op_array (就是执行逻辑)。开始执行, 以入栈的环境结构体为环境来执行。 * 并生成此函数的 符号表, 函数寻找变量, 就在符号表中寻找。即局部变量。(一个环境结构体,就对应一张符号表) * * * 注意: 函数可能调用多次。栈中可能有某函数的多个执行环境 入栈。但是 op_array 只有一个。 * */ |
静态变量
静态变量的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
// Zend/zend_compile.h struct _zend_op_array { /* Common elements */ zend_uchar type; const char *function_name; zend_class_entry *scope; zend_uint fn_flags; union _zend_function *prototype; zend_uint num_args; zend_uint required_num_args; zend_arg_info *arg_info; /* END of common elements */ zend_uint *refcount; zend_op *opcodes; zend_uint last; zend_compiled_variable *vars; int last_var; zend_uint T; zend_uint nested_calls; zend_uint used_stack; zend_brk_cont_element *brk_cont_array; int last_brk_cont; zend_try_catch_element *try_catch_array; int last_try_catch; zend_bool has_finally_block; /* static variables support */ HashTable *static_variables; zend_uint this_var; const char *filename; zend_uint line_start; zend_uint line_end; const char *doc_comment; zend_uint doc_comment_len; zend_uint early_binding; /* the linked list of delayed declarations */ zend_literal *literals; int last_literal; void **run_time_cache; int last_cache_slot; void *reserved[ZEND_MAX_RESERVED_RESOURCES]; }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// 简化 struct _zend_op_array { ... HashTable *static_variables; // 静态变量 ... } 编译后的 op_array 只有一份。 静态变量并没有存储在符号表(symbol_table)中.而是存放在op_array中。 functiont() { static$age = 1; return$age += 1; } echo t(); echo t(); echo t(); // 静态变量 不再和 执行的结构体, 也不再和 入栈的符号表有关。 |
常量
1 2 3 4 5 6 7 8 |
// Zend/zend_constants.h typedef struct _zend_constant { zval value; // 变量结构体int flags; // 标志,是否大小写敏感等char *name; // 常量名 uint name_len;// int module_number; // 模块名 } zend_constant; |
define函数的实现
define函数当然是 调用zend_register_constant声明的常量具体如下:Zend/zend_builtin_functions.c
// 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
ZEND_FUNCTION(define) { char *name; int name_len; zval *val; zval *val_free = NULL; zend_bool non_cs = 0; int case_sensitive = CONST_CS; zend_constant c; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz|b", &name, &name_len, &val, &non_cs) == FAILURE) { return; } if(non_cs) { case_sensitive = 0; } /* class constant, check if there is name and make sure class is valid & exists */ if (zend_memnstr(name, "::", sizeof("::") - 1, name + name_len)) { zend_error(E_WARNING, "Class constants cannot be defined or redefined"); RETURN_FALSE; } repeat: switch (Z_TYPE_P(val)) { case IS_LONG: case IS_DOUBLE: case IS_STRING: case IS_BOOL: case IS_RESOURCE: case IS_NULL: break; case IS_OBJECT: if (!val_free) { if (Z_OBJ_HT_P(val)->get) { val_free = val = Z_OBJ_HT_P(val)->get(val TSRMLS_CC); goto repeat; } else if (Z_OBJ_HT_P(val)->cast_object) { ALLOC_INIT_ZVAL(val_free); if (Z_OBJ_HT_P(val)->cast_object(val, val_free, IS_STRING TSRMLS_CC) == SUCCESS) { val = val_free; break; } } } /* no break */ default: zend_error(E_WARNING,"Constants may only evaluate to scalar values"); if (val_free) { zval_ptr_dtor(&val_free); } RETURN_FALSE; } c.value = *val; zval_copy_ctor(&c.value); if (val_free) { zval_ptr_dtor(&val_free); } c.flags = case_sensitive; /* non persistent */ c.name = str_strndup(name, name_len); if(c.name == NULL) { RETURN_FALSE; } c.name_len = name_len+1; c.module_number = PHP_USER_CONSTANT; if (zend_register_constant(&c TSRMLS_CC) == SUCCESS) { RETURN_TRUE; } else { RETURN_FALSE; } } // 关键代码: c.value = *val; zval_copy_ctor(&c.value); if (val_free) { zval_ptr_dtor(&val_free); } c.flags = case_sensitive; /* 大小写敏感 */ c.name = str_strndup(name, name_len); if(c.name == NULL) { RETURN_FALSE; } c.name_len = name_len+1; c.module_number = PHP_USER_CONSTANT; /* 用户定义常量 */ if (zend_register_constant(&c TSRMLS_CC) == SUCCESS) { RETURN_TRUE; } else { RETURN_FALSE; } |
常量就一个符号(哈希)表. 都使用一个符号表。所以全局有效。
常量的生成
1 2 3 4 5 6 7 |
int zend_register_constant(zend_constant *c TSRMLS_DC) { ... ... zend_hash_add(EG(zend_constants), name, c->name_len, (vaid*)c,sizeof(zend_constant, NULL) == FAILURE); ... ... } |
对象定义常量
1 2 3 4 5 6 7 8 |
classDog { public$name = 'kitty'; publicfunction__toString () { return$this->name; } } $dog = new Dog(); define('DOG', $dog); print_r(DOG); /** * define 值为对象时,会把对象装成标量来存储,需要类有 __toString魔术方法 */ |
对象
对象的底层实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
Zend/zend.h struct _zval_struct {/* Variable information */ zvalue_value value; /* value */ zend_uint refcount__gc; zend_uchar type; /* active type */ zend_uchar is_ref__gc; }; // zvaluetypede funion _zvalue_value { long lval; /* long value */ double dval; /* double value */ struct { char *val; int len; } str; HashTable *ht; /* hash table value */ zend_object_value obj; zend_ast *ast; } zvalue_value; // 在 zend.h 中 查看到 `zend_object_value obj;` 是以zend_object_value 定义. 在Zend/zend_types.h 文件中继续查看// Zend/zend_types.h 定义zend_object_value 结构体 typedef struct _zend_object_value { zend_object_handle handle; const zend_object_handlers *handlers; } zend_object_value; |
通过new出来的对象,返回的是什么。是zend_object_value. 并不是真正的对象,而是对象的指针。
返回的 handle再次指向对象。
每次new一个对象,对象就存入一张hash表中。(形象的称之为对象池)
对象存储时的特点:
1 2 3 4 5 6 7 8 9 10 11 |
// 对象 classDog { public$leg = 4; public$wei = 20; } $dog = new Dog(); // $dog 是一个对象么?// 严格说,并不是对象./** * { * handle --指向--> [hash表 {leg: 4, wei: 20}] // hash表中存在 对象 * } */ $d2 = $dog; $d2->leg = 5; echo$dog->leg, '`', $d2->leg; // 5`5// 对象并不是 引用赋值. 主要原因 zval 结构体 是再次指向一个hash表中的 对象池 $d2 = false; echo$dog->leg; // 5 |
内存分层
内存管理与垃圾回收
PHP封装了对系统内存的请求不要直接使用malloc直接请求内存
PHP函数需要内存的时候,是通过emalloc,efree. emalloc,efree向 mm_heap索要空间。
zend 中底层都离不开hash表。PHP中的HashTable太强大。
PHP 底层 所有的变量都是 放在 zend_mm_heap 中。 然后通过 各自的hash表来指向或跟踪。
zend虚拟机的运行原理
- PHP语法实现
Zend/zend_language_scanner.l Zend/zend_language_parser.y
- OPcode编译
Zend/zend.compile.c
- 执行引擎
Zend/zend_vm_* Zend/zend_execute.c
以apache模块运行时的流程