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脚本的功能。
如果文章有帮到你的话,别忘了点赞收藏噢:smile: