PHP7語言的執行原理(PHP7源碼分析)

藏色散人
發布: 2023-04-05 16:56:02
轉載
4348 人瀏覽過

PHP7語言的執行原理(PHP7源碼分析)

我們常用的高階語言有很多種,比較出名的有CC 、Python、 PHP、Go、Pascal等。而這些語言依運行的方式不同,大體分為兩種:編譯型語言和解釋型語言。

其中,編譯型語言包括CC 、Pascal、Go等。這裡說的編譯是指在應用程式原始程式執行之前,就將程式原始碼「翻譯」成組合語言,然後進一步根據軟硬體環境編譯成目標檔。一般我們稱完成編譯工作的工具叫編譯器。而解釋型語言,在程式執行時才被「翻譯」為機器語言。但是執行一次「翻譯」一次,所以執行效率較低。解釋器的工作就是解釋性語言中,負責「翻譯」原始碼的程式。

下面我們更詳細地討論一下編譯型語言和解釋性語言的運作方式。

一、編譯型語言與解釋型語言

我們知道,對於一段C語言程式碼,需要經過預編譯、編譯、彙編和鏈接,才能成為可執行的二進位。以hello.c為例:

#include<stdio.h>
int main(){   
    printf("hello world");   
    return 1;
}
登入後複製

對於這段C程式碼,main是程式入口函數,實作的功能是列印字串「hello world」 到螢幕上。編譯與執行過程如圖1所示。

PHP7語言的執行原理(PHP7源碼分析)

圖1 編譯型語言的執行示意圖

第1步:C語言程式碼預處理(例如依賴處理、宏替換等)。如以上程式碼範例,#inlcude就會在預處理階段被取代。

第2步:編譯。編譯器會把C語言翻譯成組合語言程序,一條C語言通常便以為多條彙編程式碼。同時編譯器會對程式進行最佳化,產生目標組譯程式。

第3步:編譯得到的組譯語言透過組譯器再彙編成目標程式hello.o。

第4步:連結。程式中往往包含一些共享目標文件,如範例程式中的printf()函數,位於靜態庫,需要經過連結器(如Uinx連接器ld)進行連結。

以C語言為代表的編譯型語言,程式碼發生更新都要經過以上步驟:

我們區別編譯型語言與解釋型語言,主要立足於原始碼被編譯成目標平台CPU指令的時機。對於編譯型語言,編譯結果已經是針對目前CPU體系的指令;而解釋型語言,需要先編譯成中間碼,再經由該解釋型語言的特定虛擬機,翻譯成特定CPU體系的指令被執行。解釋型語言是在運作過程中,翻譯為目標平台的指令。常說解釋型語言“慢”,主要也是慢在這裡。

在PHP7中,原始碼首先將進行詞法分析,將原始碼切割為多個字串單元,分割後的字串稱為Token。而一個獨立的Token無法表達完整語意,需經過語法分析階段,將Token轉換為抽象語法樹(簡稱AST)。之後,抽象語法樹被轉換為機器指令執行。在PHP中,這些指令稱為opcode(後文會對opcode做更詳細的解釋,此處讀者可以看待為CPU指令)。

到AST的生成這一步,編譯型語言與解釋型語言所需經歷的過程相似。從抽象語法樹之後開始產生差異。

圖2是PHP(如無特殊說明,本章提到的PHP均為PHP7版本)程式碼被執行的簡化步驟,其中最後一步的左側分支,是編譯型語言的過程。

PHP7語言的執行原理(PHP7源碼分析)

圖2 以PHP為例解釋型語言的執行示意圖

第1步:原始碼透過詞法分析得到Token;

第2步:基於語法分析器產生抽象語法樹(AST);

第3步:抽象語法樹轉換為Opcodes (opcode指令集合),PHP解釋執行Opcodes。

接下來我們在基本步驟的基礎上,細化PHP語言的執行原理,試著更清楚地建立認知。

二、PHP7的執行原理概述

首先我們補充說明下前文提到的PHP7程式執行過程,請參考圖3。

PHP7語言的執行原理(PHP7源碼分析)

圖3 PHP7語言所寫的程式的執行過程圖

#第1步:詞法分析將PHP程式碼轉換為有意義的標識Token。該步驟的詞法分析器使用Re2c實現的。

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

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

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

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

示例代码如下:

<?phpecho "hello world";
登入後複製

从图3可知,这段代码首先会被切割为Token。

1. Token

Token是PHP代码被切割成的有意义的标识。本书介绍的PHP7版本中有137 种Token,在zend_language_parser.h文件中做了定义:

/* Tokens.  */#define END 0#define T_INCLUDE 258#define T_INCLUDE_ONCE 259…#define T_ERROR 392
登入後複製

更多Token的含义,感兴趣的读者可以参考《PHP 7底层设计与源码实现》附录。

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

/home/vagrant/php7/bin/php –r &#39;print_r(Token_get_all("<?php echo \"hello world\";"));&#39;
登入後複製

输出结果为:

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] => ;
)
登入後複製

上文输出中,二维数组的每个成员数组第一个值为Token对应的枚举值;第二个值为Token对应的原始字符串内容;第三个值为代码对应的行号。可以看出,词法解析器将

1)文本“

#dfine T_OPEN_TAG 379
登入後複製

不难理解,它是PHP代码的起始tag,也就是

2)echo对应的Token是T_ECHO:

#define T_ECHO 328
登入後複製

3)源码中的空格,对应的Token叫T_WHITESPACE,值为382:

#define T_WHITESPACE 382
登入後複製

4)字符串“hello world”对应的Token值为323:

#define T_CONSTANT_ENCAPSED_STRING 323
登入後複製

可见,Token就是一个个的“词块”,但是单独存在的词块不能表达完整的语义,还需要借助规则进行组织串联。语法分析器就是这个组织者。它会检查语法、匹配Token,对Token进行关联。

PHP7中,组织串联的产物就是抽象语法树(Abstract Syntax Tree,AST)。

2. 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-...

3. Opcodes

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

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

借助vld插件,可以直观地看到一段PHP代码生成的opcode:

php -dvld.active=1 hello.php
登入後複製

经过过滤整理,对应的opcode为:

line     op              
 1      ECHO            
 2      RETURN
登入後複製

其实在源码实现中,上述代码生成的opcode及handler为:

ZEND_ECHO  // handler: ZEND_ECHO_SPEC_CONST_HANDLERZEND_RETURN  // handler: ZEND_RETURN_SPEC_CONST_HANDLER
登入後複製

可见,ZEND_ECHO对应的handler是ZEND_ECHO_SPEC_CONST_HANDLER。此handler的实现的功能便是预期的“hello world”语句的输出。

相关推荐:《PHP7新特性手册

以上是PHP7語言的執行原理(PHP7源碼分析)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:imooc.com
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新問題
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!