2 PHP的生命周期
1 请求的生命周期#
参考:
- php_module_startup 模块初始化阶段(注册内部扩展、加载外部扩展、启动附加的 PHP 扩展、启动各个模块、禁用 php.ini 里面的禁用函数和类)
- php_request_startup 请求初始化阶段 (初始化输出 handler 的栈,并把 OG (FLAGS 置为使用)、调用 zend_activate (初始化 GC、初始化编译器、初始化执行器、初始化扫描器)、对信号进行处理、设置超时时间、初始化相关全局变量)
- php_execute_script 脚本执行阶段(读取的 PHP 代码进行解析,先词法解析并使词法分析指针指向第一个位置,解析成 token,然后语法解析,生成抽象语法树,然后通过对抽象语法树进行遍历生成 opcode,opcode 在虚拟机上执行得到对应的结果)
- php_request_shutdown 请求关闭阶段(调用各个模块中注册的关闭函数和析构函数、将输出缓冲器重内容输出、调用所有扩展注册的钩子 rshutdown 函数、销毁 request 相关的全局变量、关闭编译器和执行器、还原 ini 配置)(完成这些工作,fpm 模式会循环等待请求到来)
- php_module_shutdown 模块关闭阶段(调用 sapi_flush () 然后进行销毁所有模块、全局变量、关闭扩展、销毁 ini 对应的 HashTable、关闭 ini config、关闭内存管理器、关闭输出 output、析构垃圾回收)
2 Opcodes#
在 php_execute_script 阶段会生成 Opcodes,Opcode 是一种 PHP 脚本编译后的中间语言,就像 Java 的 ByteCode ,或者 .NET 的 MSL,举个例子,比如你写下了如下的 PHP 代码:
<?php
echo "Hello World";
$a = 1 + 1;
echo $a;
?>
PHP 的语言引擎 Zend 执行这段代码会经过如下 4 个步骤:
- Scanning (Lexing) ,将 PHP 代码转换为语言片段 (Tokens)。
- Parsing, 将 Tokens 转换成简单而有意义的表达式。
- Compilation,将表达式编译成 Opocdes。
- Execution,顺次执行 Opcodes,每次一条,从而实现 PHP 脚本的功能。
2.1 Lexing#
Lex 就是一个词法分析的依据表。 Zend/zend_language_scanner.c 会根据 Zend/zend_language_scanner.l (Lex 文件), 来输入的 PHP 代码进行词法分析,从而得到一个一个的 “词”。PHP4.2 开始提供了一个函数叫 token_get_all,这个函数就可以讲一段 PHP 代码 Scanning 成 Tokens。
<?php
$phpcode = '<?php echo "Hello World"; $a = 1 + 1; echo $a; ?>';
$tokens = token_get_all($phpcode);
print_r($tokens);
// foreach ($tokens as $key => $token) {
// $tokens[$key][0] = token_name($token[0]);
// }
// print_r($tokens);
?>
token_name () 将 Token ID 转换为 Token 对应码。
Array
(
[0] => Array
(
[0] => 321 // Token ID
[1] => // 源码中的原来的内容
[2] => 1 // 该词在源码中是第几行
)
[1] => Array
(
[0] => 379
[1] => <?php
[2] => 2
)
[2] => Array
(
[0] => 382
[1] =>
[2] => 2
)
[3] => Array
(
[0] => 328
[1] => echo
[2] => 3
)
[4] => Array
(
[0] => 382
[1] =>
[2] => 3
)
[5] => Array
(
[0] => 323
[1] => "Hello World"
[2] => 3
)
......
)
2.2 Parsing#
Parsing 首先会丢弃 Tokens Array 中的多于的空格,然后将剩余的 Tokens 转换成一个一个的简单的表达式:
echo a constant string
add two numbers together
store the result of the prior expression to a variable
echo a variable
如果有安装 vld 扩展,可以使用 vld 来查看 opcodes,如下:
字段说明:
- Branch analysis from position 这条信息多在分析数组时使用。
- Return found 是否返回,这个基本上有都有
- filename 分析的文件名
- function name 函数名,针对每个函数 VLD 都会生成一段如上的独立的信息,这里显示当前函数的名称
- number of ops 生成的操作数
- compiled vars 编译期间的变量,它是一个缓存优化(以 IS_CV 标记)。
2.3 Compilation#
Compilation 会把表达式编译成 Opcodes。
在 PHP 实现内部,opcode 由如下的结构体表如下:
struct _zend_op {
opcode_handler_t handler; // 执行该 opcode 时调用的处理函数
znode result; // 存放结果
znode op1; // 操作数1
znode op2; // 操作数2
ulong extended_value; // 扩展
uint lineno;
zend_uchar opcode; // opcode代码
};
opcodes 保存在 op_array 中,其内部存储的结构如下:
struct _zend_op_array {
......
zend_op *opcodes; // opcode 数组
}
2.4 Execution#
Zend 引擎调用 zend_executor 来顺序执行 Opcodes,每次一条,从而实现 PHP 脚本的功能。
如果文章有帮到你的话,别忘了点赞收藏噢
推荐文章: