溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

PHP7語言的執(zhí)行原理是什么

發(fā)布時間:2020-10-29 11:16:20 來源:億速云 閱讀:159 作者:小新 欄目:編程語言

這篇文章給大家分享的是有關(guān)PHP7語言的執(zhí)行原理是什么的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考。一起跟隨小編過來看看吧。

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

其中,編譯型語言包括CC++、Pascal、Go等。這里說的編譯是指在應(yīng)用源程序執(zhí)行之前,就將程序源代碼“翻譯”成匯編語言,然后進一步根據(jù)軟硬件環(huán)境編譯成目標文件。一般我們稱完成編譯工作的工具叫編譯器。而解釋型語言,在程序運行時才被“翻譯”為機器語言。但是執(zhí)行一次“翻譯”一次,所以執(zhí)行效率較低。解釋器的工作就是解釋性語言中,負責(zé)“翻譯”源代碼的程序。

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

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

我們知道,對于一段C語言代碼,需要經(jīng)過預(yù)編譯、編譯、匯編和鏈接,才能成為可執(zhí)行的二進制文件。以hello.c為例:

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

對于這段C代碼,main是程序入口函數(shù),實現(xiàn)的功能是打印字符串“hello world” 到屏幕上。編譯和執(zhí)行過程如圖1所示。

PHP7語言的執(zhí)行原理是什么

圖1 編譯型語言的執(zhí)行示意圖

第1步:C語言代碼預(yù)處理(比如依賴處理、宏替換等)。如以上代碼示例,#inlcude<stdio.h>就會在預(yù)處理階段被替換。

第2步:編譯。編譯器會把C語言翻譯成匯編語言程序,一條C語言通常便以為多條匯編代碼。同時編譯器會對程序進行優(yōu)化,生成目標匯編程序。

第3步:編譯得到的匯編語言通過匯編器再匯編成目標程序hello.o。

第4步:鏈接。程序中往往包含一些共享目標文件,如示例程序中的printf()函數(shù),位于靜態(tài)庫,需要經(jīng)過鏈接器(如Uinx連接器ld)進行鏈接。

以C語言為代表的編譯型語言,代碼發(fā)生更新都要經(jīng)過以上步驟:

我們區(qū)別編譯型語言與解釋型語言,主要立足于源代碼被編譯成目標平臺CPU指令的時機。對于編譯型語言,編譯結(jié)果已經(jīng)是針對當前CPU體系的指令;而解釋型語言,需要先編譯成中間代碼,再經(jīng)由該解釋型語言的特定虛擬機,翻譯成特定CPU體系的指令被執(zhí)行。解釋型語言是在運行過程中,翻譯為目標平臺的指令。常說解釋型語言“慢”,主要也是慢在這里。

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

到AST的生成這一步,編譯型語言與解釋型語言所需經(jīng)歷的過程相似。從抽象語法樹之后開始產(chǎn)生差異。

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

PHP7語言的執(zhí)行原理是什么

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

第1步:源碼通過詞法分析得到Token;

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

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

接下來我們在基本步驟的基礎(chǔ)上,細化PHP語言的執(zhí)行原理,試圖更清晰地建立認知。

二、PHP7的執(zhí)行原理概述

首先我們補充說明下前文提到的PHP7程序執(zhí)行過程,請參見圖3。

PHP7語言的執(zhí)行原理是什么

圖3 PHP7語言編寫的程序的執(zhí)行過程圖

第1步:詞法分析將PHP代碼轉(zhuǎn)換為有意義的標識Token。該步驟的詞法分析器使用Re2c實現(xiàn)的。

第2步:語法分析將Token和符合文法規(guī)則的代碼生成抽象語法樹。語法分析器基于Bison實現(xiàn)。語法分析使用了巴科斯范式(BNF)來表達文法規(guī)則,Bison借助狀態(tài)機、狀態(tài)轉(zhuǎn)移表和壓棧、出棧等一系列操作,生成抽象語法樹。

第3步:上步的抽象語法樹生成對應(yīng)的opcode,被虛擬機執(zhí)行。opcode是PHP7定義的一組指令標識,指令對應(yīng)著相應(yīng)的handler(處理函數(shù))。當虛擬機調(diào)用opcode,會找到opcode背后的處理函數(shù),執(zhí)行真正的處理。以我們常見的echo語句為例,其對應(yīng)的opcode便是ZEND_ECHO。

注意:這里為了便于理解詞法分析和語法分析過程,將兩者分開描述。但實際情況,出于效率考慮,兩個過程并非完全獨立。

下面,我們通過一段示例代碼,來建立PHP7運轉(zhuǎn)的初步理解。

示例代碼如下:

<?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底層設(shè)計與源碼實現(xiàn)》附錄。

PHP提供了token_get_all()函數(shù)來獲取PHP代碼被切割后的Token,可以在深入源碼學(xué)習(xí)前,粗略查看PHP代碼被切割后的Token。如下代碼片段:

/home/vagrant/php7/bin/php –r 'print_r(Token_get_all("<?php echo \"hello world\";"));'

輸出結(jié)果為:

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] => ;
)

上文輸出中,二維數(shù)組的每個成員數(shù)組第一個值為Token對應(yīng)的枚舉值;第二個值為Token對應(yīng)的原始字符串內(nèi)容;第三個值為代碼對應(yīng)的行號??梢钥闯?,詞法解析器將 <?php echo "hello world"; 這段文本內(nèi)容切分成了4部分。

1)文本“<?php”,切割后對應(yīng)的Token值為379,參考PHP7中的源碼:

#dfine T_OPEN_TAG 379

不難理解,它是PHP代碼的起始tag,也就是<?php標識;

2)echo對應(yīng)的Token是T_ECHO:

#define T_ECHO 328

3)源碼中的空格,對應(yīng)的Token叫T_WHITESPACE,值為382:

#define T_WHITESPACE 382

4)字符串“hello world”對應(yīng)的Token值為323:

#define T_CONSTANT_ENCAPSED_STRING 323

可見,Token就是一個個的“詞塊”,但是單獨存在的詞塊不能表達完整的語義,還需要借助規(guī)則進行組織串聯(lián)。語法分析器就是這個組織者。它會檢查語法、匹配Token,對Token進行關(guān)聯(lián)。

PHP7中,組織串聯(lián)的產(chǎn)物就是抽象語法樹(Abstract Syntax Tree,AST)。

2. AST

AST是PHP7版本新特性。在這之前的版本,PHP代碼的執(zhí)行過程中沒有生成AST這一步。PHP7對抽象語法樹的支持,實現(xiàn)了PHP編譯器和解釋器解耦,有效提升了可維護性。

顧名思義,抽象語法樹具有樹狀結(jié)構(gòu)。AST的節(jié)點分為多種類型,對應(yīng)著不同的PHP語法。在當前章節(jié),我們可以認為節(jié)點類型是對語法規(guī)則的抽象,例如賦值語句,生成的抽象語法樹節(jié)點為ZEND_AST_ASSIGN。而賦值語句的左右操作數(shù),又將作為ZEND_AST_ASSIGN類型節(jié)點的孩子。通過這樣的節(jié)點關(guān)系,構(gòu)建出抽象語法樹。

如果讀者希望一睹為快,可以直接跳到本書第13章函數(shù)的實現(xiàn),其中圖片描繪了一段簡單的PHP代碼生成的抽象語法樹。

在這里,我們推薦讀者了解下PhpParser工具,可以用它來查看PHP代碼生成的AST。

注意:PHP-Parser是PHP7內(nèi)核作者之一nikic編寫的將PHP源碼生成AST的工具。源碼見https://github.com/nikic/PHP-...

3. Opcodes

AST扮演了源碼到中間代碼的臨時存儲介質(zhì)的角色,還需要將其轉(zhuǎn)換為opcode,才能被引擎直接執(zhí)行。Opcode只是單條指令,Opcodes是opcode的集合形式,是PHP執(zhí)行過程中的中間代碼,類似Java中的字節(jié)碼。生成之后由虛擬機執(zhí)行。

我們知道,PHP工程優(yōu)化措施中有個比較常見的“開啟Opcache”,指的就是這里的Opcodes的緩存(Opcodes Cache)。通過省去從源碼到opcode的階段,引擎可以直接執(zhí)行緩存的opcode,以此提升性能。

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

php -dvld.active=1 hello.php

經(jīng)過過濾整理,對應(yīng)的opcode為:

line     op              
 1      ECHO            
 2      RETURN

其實在源碼實現(xiàn)中,上述代碼生成的opcode及handler為:

ZEND_ECHO  // handler: ZEND_ECHO_SPEC_CONST_HANDLERZEND_RETURN  // handler: ZEND_RETURN_SPEC_CONST_HANDLER

可見,ZEND_ECHO對應(yīng)的handler是ZEND_ECHO_SPEC_CONST_HANDLER。此handler的實現(xiàn)的功能便是預(yù)期的“hello world”語句的輸出。

感謝各位的閱讀!關(guān)于PHP7語言的執(zhí)行原理是什么就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI