PHP-Zend引擎剖析之Hello World(二)
前言
这一次,我围绕Hello World来展开Zend虚拟机的执行过程。Hello World的PHP版本:<?php echo "Hello World"; ?>
前一篇文章聊到的词法分析阶段就会把上边的脚本分析出一个Token序列:

跟踪运行轨迹
我们还是从命令行入手,在$PHPSRC/sapi/cli/php_cli.c中的do_cli函数里边接收了命令行的参数输入(php -f HelloWorld.php表示执行HelloWorld.php文件)。

EG(active_op_array) = zend_compile_file(file_handle, type TSRMLS_CC);zend_execute(EG(active_op_array) TSRMLS_CC);
首先通过zend_compile_file把文件解析成opcode中间代码(这一步会经过词法语法分析),然后用zend_execute执行这个生成的中间代码(这里就是所谓的运行时)。 这里很像C语言的编译方式,先编译成汇编,然后再转成机器码,这里的opcode就类似C语言编译过程中生成的汇编。 还可以延伸出一个思路,因为每次解析PHP文件时,都需要经过词法语法分析得到对应的opcode,其实在脚本文件不变化的时候,生成的opcode也不需要变化,因此为了减少PHP脚本的执行时间,可以把脚本的opcode缓存起来(例如缓存在共享内存里边)。 我给出一个流程图,然后随着这个流程图,看看Zend做了些什么事情:

词法语法分析->opcode
从上节知道我们通过zend_compile_file(实际上为compile_file()<定义在Zend/zend_language_scanner.c的555行>)把脚本文件编译出opcode,实际上通过zendparse这个API来编译出opcode的。
bison -o zend_language_parser.c zend_language_parser.y
在Zend目录下就会生成语法解析器zend_language_parser.c。而这里的zendparse就是语法解析器里边的yyparse! 我们忽略掉生成的语法解析器,就Hello World的例子来跟踪一下bison的声明文件(我去掉不想关的声明):
start: top_statement_list { zend_do_end_compilation(TSRMLS_C); } ;top_statement_list: top_statement_list { zend_do_extended_info(TSRMLS_C); } top_statement { HANDLE_INTERACTIVE(); } | /* empty */ ;top_statement: statement { zend_verify_namespace(TSRMLS_C); } ;statement: unticked_statement { DO_TICKS(); } | T_STRING ":" { zend_do_label(&$1 TSRMLS_CC); } ;unticked_statement: | T_ECHO echo_expr_list ";"echo_expr_list: echo_expr_list "," expr { zend_do_echo(&$3 TSRMLS_CC); } | expr { zend_do_echo(&$1 TSRMLS_CC); } ; expr: r_variable { $$ = $1; } | expr_without_variable { $$ = $1; } ; expr_without_variable: | scalar { $$ = $1; } scalar: | common_scalar { $$ = $1; } ; common_scalar: | T_CONSTANT_ENCAPSED_STRING { $$ = $1; } ;
语法分析从start开始,自上而下的分析,一个PHP脚本就是对应一个top_statement_list,接着分成每一行一条语句statement,发现echo "Hello World"是一条unticked_statement(留意一下echo_expr_list的声明, 我们还可以发现语法上是支持echo "Hello", " World"的)。最后递归到T_CONSTANT_ENCAPSED_STRING状态就结束了这一行的语法解析。在这里我们忽略掉编译原理在语法分析阶段是怎么去做回溯等等东西,我们关注一下Zend引擎自身的的问题。 在规则后边的块"{}"里边的代码就是用来处理扫描到此规则时的动作,可以看到echo的执行是调用了zend_do_echo函数的。在动作声明的块里边我们看到了$$, $1,$2,$3等,这些对应的就是该条规则里边的返回值,参数1,参数2……,这里的返回值以及参数都是YYSTYPE类型,这个类型在43行里边有定义:#define YYSTYPE znode。znode的定义在zend_compile.h里边:





void zend_init_opcodes_handlers(void){static const opcode_handler_t labels[] = {//40913行ZEND_ECHO_SPEC_CONST_HANDLER,//41914行ZEND_ECHO_SPEC_CONST_HANDLER,ZEND_ECHO_SPEC_CONST_HANDLER,ZEND_ECHO_SPEC_CONST_HANDLER,ZEND_ECHO_SPEC_CONST_HANDLER};
请花费短暂的时间先记住这里的labels以及行数。 发现了获取handler的方法最后边return语句的计算,根据前面说的echo的opcode是40(假设两个参数op1,op2的type都是0),于是乎其对应的handler就是:

虚拟机执行opcode
前边我们已经解释了zend_compile_file把一个脚本编译成一个opcode的list:EG(active_op_array) = zend_compile_file(file_handle, type TSRMLS_CC);zend_execute(EG(active_op_array) TSRMLS_CC);
在这之后,Zend引擎用zend_execute执行返回的opcode。 我们定位到了zend_execute最后执行到Zend/zend_vm_execute.h的337行:

zend_write = (zend_write_func_t) utility_functions->write_function;
这里的utility_functions里边包含了一些基础的handler,每个sapi接入层自己修改了这里的基础函数指针,例如在命令行模式下,最后调用到了 sapi_cli_single_write:

结语
最后根据前边的过程,再展开一下流程图就是:
声明:该文观点仅代表作者本人,入门客AI创业平台信息发布平台仅提供信息存储空间服务,如有疑问请联系rumenke@qq.com。
- 上一篇: PHP-Zend引擎剖析之词法分析(一)
- 下一篇:没有了