PHP7内核学习--语言的执行原理

版权声明:让我们一起享受这神奇的0和1吧 https://blog.csdn.net/eebaicai/article/details/83660536

我们常用的高级语言有很多种,比较出名的有C\C++、Python、PHP、Go、Pascal等。而这些语言根据运行的方式不同,大体分为两种:编译型语言和解释型语言
其中,编译型语言包括C\C++、Pascal、Go等。这里说的编译是指在应用源程序执行之前,就将程序源代码编译成汇编语言,然后进一步根据软硬件环境"翻译"成目标文件。一般称完成编译工作的工具为编译器。而解释型语言,在程序运行时才被“翻译”为机器语言。但是执行一次“翻译”一次,所以执行效率较低。解释器的工作就是解释型语言中,负责“翻译”源代码的程序

在这里插入图片描述

我们对编译型语言与解释型语言的区别的理解,立足于源代码被编译成目标平台CPU指令的时机。对于编译型语言,编译结果已经是针对当前CPU体系的指令;而解释型语言,需要先编译成中间代码,再经由该解释型语言的特定虚拟机,翻译成特定CPU体系的指令被执行。解释型语言是在运行过程中,翻译为目标平台的指令。常说解释型语言“慢”,主要也是慢在这里

php7中执行原理

在PHP 7中,源代码首先进行词法分析,将源代码切割为多个字符串单元分割后的字符串称为Token。而一个一个独立的Token无法表达完整语义的,需经过语法分析阶段,将Token转换为抽象语法树(简称AST)。之后,抽象语法树被转换为机器指令执行。在PHP中,这些指令称为opcode(以后会对opcode做更详细的解释,此处可以将其看待为CPU指令)。
AST的生成这一步,编译型语言与解释型语言所需经历的过程相似。从抽象语法树之后开始产生差异。
简图(最后一步的左侧分支是编译型语言的过程)
在这里插入图片描述
第1步:源码通过词法分析得到Token。
第2步:基于语法分析器生成抽象语法树(AST)。
第3步:抽象语法树转换为opcodes(opcode指令集合),PHP解释执行opcodes。

接下来在基本步骤的基础上,细化PHP语言的执行原理,以便更清晰地建立认知。

第1步:词法分析将PHP代码转换为有意义的标识Token。该步骤的词法分析器使用Re2c实现。

第2步:语法分析将Token和符合文法规则的代码生成抽象语法树语法分析器基于Bison实现。语法分析使用了BNF(Backus-Naur Form,巴科斯范式)来表达文法规则,Bison借助状态机状态转移表压栈、出栈等一系列操作,生成抽象语法树。

第3步:上步的抽象语法树生成对应的opcode,并被虚拟机执行。opcode是PHP 7定义的一组指令标识,指令对应着相应的handler(处理函数)。当虚拟机调用opcode,会找到opcode背后的处理函数,执行真正的处理。以常见的echo语句为例,其对应的opcode便是ZEND_ECHO。

注意 这里为了便于理解词法分析和语法分析过程,将两者分开描述。但实际情况下,出于效率考虑,两个过程并非完全独立

在这里插入图片描述

扫描二维码关注公众号,回复: 4180999 查看本文章

下面通过一段示例代码,来建立PHP 7运转的初步理解。

echo "hello world";

这段代码首先会被切割为Token
Token

Token是PHP代码被切割成的有意义的标识。本次使用的PHP 7版本中有137种Token,在zend_language_parser.h文件中做了定义
在这里插入图片描述
http://php.net/manual/zh/tokens.php

PHP提供了token_get_all()函数来获取PHP代码被切割后的Token,可以在深入源码学习前,粗略查看PHP代码被切割后的Token。对于如下代码片段:

./php -r 'print_r(token_get_all("<?php echo \"hello world\";?>"));'

//输出:
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 world"
            [2] => 1
        )

    [4] => ;
    [5] => Array
        (
            [0] => 381
            [1] => ?>
            [2] => 1
        )

)

其中,二维数组的每个成员数组的第一个值为Token对应的枚举值第二个值为Token对应的原始字符串内容第三个值为代码对应的行号。可以看出,词法解析器将“<?php echo"hello world";”这段文本内容切分成了4部分。
1)文本“<?php”,切割后对应的Token值为379,参考PHP 7中的源码:
在这里插入图片描述
2)echo对应的Token是T_ECHO,对应的Token值为328:
在这里插入图片描述
3)源码中的空格,对应的Token为T_WHITESPACE,值为382:
在这里插入图片描述
4)字符串"hello world",对应的Token值为323:
在这里插入图片描述

可见,Token就是一个个的“词块”,但是单独存在的词块不能表达完整的语义,还需要借助规则进行组织串联。语法分析器就是这个组织者。它会检查语法,匹配Token,对Token进行关联
PHP 7中,组织串联产物就是AST(Abstract Syntax Tree,抽象语法树)。

AST

AST是PHP 7版本新特性。在这之前的版本中,PHP代码的执行过程中是没有生成AST这一步的。PHP 7对抽象语法树的支持,实现了PHP编译器和解释器解耦,有效提升了可维护性
顾名思义,抽象语法树具有树状结构。AST的节点分为多种类型,对应着PHP语法。我们可以认为节点类型对语法规则的抽象,例如赋值语句,生成的抽象语法树节点为ZEND_AST_ASSIGN。而赋值语句的左右操作数又将作为ZEND_AST_ASSIGN类型节点的孩子。通过这样的节点关系,构建出抽象语法树。

PHP-Parser工具,它可以用来查看PHP代码生成的AST。
注意 PHP-Parser是PHP 7内核作者之一Nikic编写的将PHP源码生成AST的工具。源码见https://github.com/nikic/PHP-Parser。

opcodes

AST扮演了源码到中间代码的临时存储介质的角色,还需要将其转换为opcode,才能被引擎直接执行opcode只是单条指令,opcodes是opcode的集合形式,是PHP执行过程中的中间代码,类似Java中的字节码。opcode生成之后由虚拟机执行。

我们知道,PHP工程优化措施中有一个比较常见的“开启opcache”,指的就是这里的opcodes的缓存(opcodes cache)。通过省去从源码到opcode的阶段,引擎可以直接执行缓存的opcode,以此提升性能

借助vld插件,可以直观地看到一段PHP代码生成的opcode:(未完待续)

猜你喜欢

转载自blog.csdn.net/eebaicai/article/details/83660536
今日推荐