FPM 启动和初始化 worker 的过程
代码在源码 /sapi/fpm/fpm/fpm_main.c 中
- fpm_conf_init_main() 函数解析 php-fpm.conf 配置文件,调配 worker pool 的内存空间。每个worker pool 用构造体 fpm_worker_pool_s 示意,每个 pool 中的有一个 fpm_scoreboard_s 构造体,用来治理具体一个 worker
- fpm_scoreboard_init_main() 函数调配每个 worker 的内存,在 pool 的 fpm_scoreboard_s 构造体中,每个 worker 应用 fpm_scoreboard_proc_s 构造体示意
- 之后 master 给每个 worker pool 创立 socket、注册监听的信号等。
即解析 php-fpm.conf → 初始化worker pool→ 初始化worker
关系相似:fpm_worker_pool_s(fpm_scoreboard_s(fpm_scoreboard_proc_s))
FPM worker/PHP 解决申请的过程
- 期待申请:worker 阻塞在
fcgi_accept_request
函数期待申请到来。 - 解析申请:FastCGI 接到申请并解析申请数据。
- 申请初始化:执行
php_request_startup
此阶段会调用每个扩大的:PHP_RINIT_FUNCTION
函数 即 RINIT。 - 编译、执行:由
php_execute_script
实现 PHP 脚本的编译、执行 - 敞开申请:申请实现后执行
php_request_shutdown
,此阶段会调用每个扩大的:PHP_RSHUTDOWN_FUNCTION
即 RSHUTDOWN,从新进入下一轮期待。
// master执行本函数 创立并初始化workerint fpm_run(int *max_requests){ struct fpm_worker_pool_s *wp; for (wp = fpm_worker_all_pools; wp; wp = wp->next) { //调用fpm_children_make() fork子过程 is_parent = fpm_children_create_initial(wp); if (!is_parent) { goto run_child; } } //master过程将进入event循环,不再往下走 fpm_event_loop(0);run_child: //只有worker过程会到这里 *max_requests = fpm_globals.max_requests; return fpm_globals.listening_socket; //返回监听的套接字}int main(int argc, char *argv[]){ ... fcgi_fd = fpm_run(&max_requests); parent = 0; //初始化fastcgi申请 request = fpm_init_request(fcgi_fd); //worker过程将阻塞在这,期待申请 while (EXPECTED(fcgi_accept_request(request) >= 0)) { SG(server_context) = (void *) request; init_request_info(); //申请开始 RINIT if (UNEXPECTED(php_request_startup() == FAILURE)) { ... } ... fpm_request_executing(); //编译、执行PHP脚本 php_execute_script(&file_handle); ... //申请完结 RSHUTDOWN php_request_shutdown((void *) 0); ... } ... //worker过程退出 MSHUTDOWN php_module_shutdown(); ...}
PHP 执行的几个阶段或生命周期
从申请放宽到整个 PHP 的执行阶段,在下面申请的解决前后,减少模块的初始化和敞开阶段:
模块初始化 php_module_startup MINIT
- 初始化各个模块
- 初始化局部全局变量和常量
- 解析php.ini
- 初始化 Zend 引擎和外围组件
申请初始化 php_request_startup RINIT
- 重置垃圾回收
- 初始化编译器、执行器、扫描器等
执行 PHP 脚本 php_execute_script EXEC
- 词法剖析,失去 tokens
- 语法分析 失去 形象语法树 AST
- 编译成 opcodes
- 执行 opcodes
申请完结 php_request_shutdown RSHUTDOWN
- 销毁request相干的全局变量
- 敞开编译器、执行器
- 还原ini配置
模块敞开 php_module_shutdown MSHUTDOWN
- 销毁全局变量
- 敞开所有扩大、垃圾回收、内存治理等
Token、AST 和 opcodes 之间的关系
PHP 代码 => Token => 形象语法树 => Opcodes => 执行
- 源代码通过词法剖析失去 Token
Token 是 PHP 代码被切割成的有意义的标识。PHP7 一共有 137 种 Token,在 zend_language_parser.h 文件中做了定义。 - 基于语法分析器将 Token 转换成形象语法树(AST)
Token 就是一个个的词块,然而独自的词块不能表白残缺的语义,还须要借助肯定的规定进行组织串联。所以就须要语法分析器依据语法匹配 Token,将 Token 进行串联。语法分析器串联完 Token 后的产物就是形象语法树(AST,Abstract Syntax Tree)。
AST 是 PHP7 版本的新个性,之前版本的 PHP 代码的执行过程中是没有生成 AST 这一步的。它的作用次要是实现了 PHP 编译器和解释器的解耦,晋升了可维护性。 - 将语法树转换成 Opcode
须要将语法树转换成 Opcode,能力被引擎间接执行。 - 执行 Opcodes
opcodes 是 opcode 的汇合模式,是 PHP 执行过程中的中间代码。PHP 工程优化措施中有一个比拟常见的 “开启 opcache”,指的技术这里将 opcodes 进行缓存。通过省去从源码到 opcode 的阶段,引擎间接执行缓存好的 opacode,以晋升性能。
Token 例子
如下代码中,通过第一部词法剖析后失去一些token
php -r 'print_r(Token_get_all("<?php echo \"hello\"; "));'
输入:
Array( [0] => Array ( [0] => 379 [1] => <?php [2] => 1 ) [1] => Array ( [0] => 328 [1] => echo [2] => 1 ) [2] => Array ( [0] => 382 [1] => [2] => 1 ) [3] => Array ( [0] => 323 [1] => "hello" [2] => 1 ) [4] => ; [5] => Array ( [0] => 382 [1] => [2] => 1 ))
Token_get_all 函数能够打印解析的 token。数组的第一个值为 Token 对应的枚举值。第二个值为 Token 对应的原始字符串内容。第三个值为代码对应的行号。能够看出,词法解析器将 “<? php echo "hello world"; ” 这段文本内容切分成了 4 局部。
AST 形象语法树
之后生成 AST 形象语法树,可了解为对语法的一种形象,再用 AST 生成 opcodes,是 opcode 的汇合,交给 zend 执行。如这样一段代码:
<?php$a = 1;$b = $a + 2;echo $b;
生成的 AST 大略这个意思:
如果应用 php-parser 解析生成 AST,后果这样:
array( 0: Stmt_Expression( expr: Expr_Assign( var: Expr_Variable( name: a ) expr: Scalar_LNumber( value: 1 ) ) ) 1: Stmt_Expression( expr: Expr_Assign( var: Expr_Variable( name: b ) expr: Expr_BinaryOp_Plus( left: Expr_Variable( name: a ) right: Scalar_LNumber( value: 2 ) ) ) ) 2: Stmt_Echo( exprs: array( 0: Expr_Variable( name: b ) ) ))
opcode
PHP 是构建在 Zend 虚拟机(Zend VM)之上的,PHP 的 opcode 就是 Zend 虚拟机中的指令。
在 php_execute_script 执行脚本阶段,会先通过词法剖析,失去 tokens,之后进行语法分析,之后失去 opcode,最初交给执行器执行 opcode。
应用 vld 扩大可查看生成的 opcode
php -dvld.active=1 b.php
附加-程序执行的词法剖析和语法分析介绍
词法剖析是把 PHP 代码宰割成一个个的“单元”(TOKEN),语法分析则将这些“单元”转化为 Zend Engine 可执行的操作。而后 PHP 外部的 Zend Engine 对这些操作进行依次的执行。
词法剖析和语法分析个别按应用 Lex 和 Yacc 来实现。
- Lex(Lexical Analyzer)次要用于做词法剖析
- Yacc(Yet Another Compiler-Compiler)次要用来做语法分析
对于 lex yacc 的解释:https://www.cnblogs.com/hdk1993/p/4922801.html
系列文章《Python解释器源码分析》https://www.cnblogs.com/traditional/p/11511685.html
本文由mdnice多平台公布