当前位置:首页 > 企业简介 >

PHP-Zend发动机剖析之Hello World(二)

编辑:北京盛典时光文化传媒有限公司时间:2017-09-06 10:53:53阅读次数:2
PHP-Zend发动机剖析之Hello World(二) PHP-Zend引擎剖析之Hello World(二)

前言

这一次,我围绕Hello World来展开Zend虚拟机的执行过程。Hello World的PHP版本:

? ? ?echo 'Hello World';

?>

前一篇文章聊到的词法分析阶段就会把上边的脚本分析出一个Token序列:

\

我们得到一个Token序列:T_OPEN_TAG, T_ECHO, T_CONSTANT_ENCAPSED_STRING, ';', T_CLOSE_TAG。但在Zend虚拟机执行的过程中,是怎么去分析这个Token序列的?

跟踪运行轨迹

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

\


\

我们追踪到$PHPSRC/main/main.c里边有php_execute_script的定义,紧接着调用了zend_execute_scripts() ,在zend_execute_scripts的定义里边我们发现了:

?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的。

词法语法分析->opcode

从上节知道我们通过zend_compile_file(实际上为compile_file()<定义在Zend/zend_language_scanner.c的555行>)把脚本文件编译出opcode,实际上通过zendparse这个API来编译出opcode的。

\

PHP的语法解析器是用bison来生成,安装完之后在$PHPSRC/Zend目录运行:

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里边:

\

留意到zend_op这个结构,于是跟踪发现这个就是最后每条语句对应的opcode结构了!!!!

\


opcode的结构跟汇编有很大的相似之处,一个操作符,两个操作数。

在Zend引擎中,每个opcode主要的东西就是那个handler,一会我们会看到Zend里边是怎么生成这个handler的。到了这里先Hold住一下,回过头,我们看一下Hello World这个例子生成的opcode是什么。

装上vld,然后运行:php -dvld.active=1 HelloWorld.php,我们就可以看到这个PHP文件编译出来的opcode列表了:

\

可以看到echo这个语句的opcode类型是ECHO,同时return没有返回值,只有一个操作数"Hello World"。

现在经过了语法分析,我们对每条语句都编译出了opcode,Zend就会把它放入一个op_array里边(其实就是一个opcode的列表)。

回过头我们看一下zend_do_echo做了什么事情:

\

首先通过get_next_op在当前的op_array的最后边生成一条opcode,然后设置其opcode类型是ZEND_ECHO,然后设置它的第一个参数op1,同时标记第二个参数op2是不需要使用的(unused的)。

经过了这么多步骤之后我们得到了一个op_array的列表,这个列表里边的每一条opcode都绑定了自己的类型,接着我们看一下每个opcode节点是如何绑定handler的。

zend_vm_def.h定义了ZEND_ECHO的handler,留意到这里的40,一会需要用到,因为echo的参数可以有几种:常量,变量等等,所以对应着不同的handler

\

在zend_vm_execute.h定义了opcode对应的所有的handler,我们只关注echo相关的handler,注意到其中的代码:

企业建站2800元起,携手武汉肥猫科技,做一个有见地的颜值派!更多优惠请戳:仙桃SEO http://xiantao.raoyu.net

上一篇:.Net和SqlServer的事务处理实例 下一篇:最后一页

相关阅读