Phper
Victor
xcent'blog【PHP7源码分析】PHP7语言的执行原理
PHP(如无特殊说明,本章提到的PHP均为PHP7版本)代码被执行的简化步骤,其中最后一步的左侧分支,是编译型语言的过程。
以PHP为例解释型语言的执行示意图
第1步:源码通过词法分析得到Token;
第2步:基于语法分析器生成抽象语法树(AST);
第3步:抽象语法树转换为Opcodes(opcode指令集合),PHP解释执行Opcodes。
接下来我们在基本步骤的基础上,细化PHP语言的执行原理,试图更清晰地建立认知。
PHP7语言编写的程序的执行过程图
第1步:词法分析将PHP代码转换为有意义的标识Token。该步骤的词法分析器使用Re2c实现的。
第2步:语法分析将Token和符合文法规则的代码生成抽象语法树。语法分析器基于Bison实现。语法分析使用了巴科斯范式(BNF)来表达文法规则,Bison借助状态机、状态转移表和压栈、出栈等一系列操作,生成抽象语法树。
第3步:上步的抽象语法树生成对应的opcode,被虚拟机执行。opcode是PHP7定义的一组指令标识,指令对应着相应的handler(处理函数)。当虚拟机调用opcode,会找到opcode背后的处理函数,执行真正的处理。以我们常见的echo语句为例,其对应的opcode便是ZEND_ECHO。
注意:这里为了便于理解词法分析和语法分析过程,将两者分开描述。但实际情况,出于效率考虑,两个过程并非完全独立。
下面,我们通过一段示例代码,来建立PHP7运转的初步理解。
这段代码首先会被切割为Token。
- Token
Token是PHP代码被切割成的有意义的标识。本书介绍的PHP7版本中有137 种Token,在zend_language_parser.h文件中做了定义:
更多Token的含义,感兴趣的读者可以参考《PHP 7底层设计与源码实现》附录。
PHP提供了token_get_all()函数来获取PHP代码被切割后的Token,可以在深入源码学习前,粗略查看PHP代码被切割后的Token。如下代码片段:
输出结果为:
上文输出中,二维数组的每个成员数组第一个值为Token对应的枚举值;第二个值为Token对应的原始字符串内容;第三个值为代码对应的行号。可以看出,词法解析器将 <?php echo "hello world"; 这段文本内容切分成了4部分。
1)文本“<?php”,切割后对应的Token值为379,参考PHP7中的源码:
不难理解,它是PHP代码的起始tag,也就是<?php标识;
2)echo对应的Token是T_ECHO:
3)源码中的空格,对应的Token叫T_WHITESPACE,值为382:
4)字符串“hello world”对应的Token值为323:
可见,Token就是一个个的“词块”,但是单独存在的词块不能表达完整的语义,还需要借助规则进行组织串联。语法分析器就是这个组织者。它会检查语法、匹配Token,对Token进行关联。
PHP7中,组织串联的产物就是抽象语法树(Abstract Syntax Tree,AST)。
- AST
AST是PHP7版本新特性。在这之前的版本,PHP代码的执行过程中没有生成AST这一步。PHP7对抽象语法树的支持,实现了PHP编译器和解释器解耦,有效提升了可维护性。
顾名思义,抽象语法树具有树状结构。AST的节点分为多种类型,对应着不同的PHP语法。在当前章节,我们可以认为节点类型是对语法规则的抽象,例如赋值语句,生成的抽象语法树节点为ZEND_AST_ASSIGN。而赋值语句的左右操作数,又将作为ZEND_AST_ASSIGN类型节点的孩子。通过这样的节点关系,构建出抽象语法树。
如果读者希望一睹为快,可以直接跳到本书第13章函数的实现,其中图片描绘了一段简单的PHP代码生成的抽象语法树。
在这里,我们推荐读者了解下PhpParser工具,可以用它来查看PHP代码生成的AST。
注意:PHP-Parser是PHP7内核作者之一nikic编写的将PHP源码生成AST的工具。源码见https://github.com/nikic/PHP-...
- Opcodes
AST扮演了源码到中间代码的临时存储介质的角色,还需要将其转换为opcode,才能被引擎直接执行。Opcode只是单条指令,Opcodes是opcode的集合形式,是PHP执行过程中的中间代码,类似Java中的字节码。生成之后由虚拟机执行。
我们知道,PHP工程优化措施中有个比较常见的“开启Opcache”,指的就是这里的Opcodes的缓存(Opcodes Cache)。通过省去从源码到opcode的阶段,引擎可以直接执行缓存的opcode,以此提升性能。
借助vld插件,可以直观地看到一段PHP代码生成的opcode:
其实在源码实现中,上述代码生成的opcode及handler为:
可见,ZEND_ECHO对应的handler是ZEND_ECHO_SPEC_CONST_HANDLER。此handler的实现的功能便是预期的“hello world”语句的输出。
本书的PHP版本中,内核在zend_vm_opcodes.h中定义了186种Opcodes
在平时的业务开发中,了解一些 PHP的底层实现,尤其是语法机制的实现,对性能提升、故障排除非常有好处。 希望这篇浅文,可以帮助读者在使用PHP7的同时,了解到编写的PHP代码如何被编译和执行。