国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

【PHP7源碼分析】PHP7源碼研究之淺談Zend虛擬機(jī)

馬龍駒 / 1273人閱讀

摘要:中詞法語法分析,生成抽象語法樹,然后編譯成及被執(zhí)行均由虛擬機(jī)完成。通常情況下這部分是可選部分,主要為便于程序的讀寫方便而使用。指令虛擬機(jī)的指令稱為,每條指令對(duì)應(yīng)一個(gè)。

作者 陳雷
編程語言的虛擬機(jī)是一種可以運(yùn)行中間語言的程序。中間語言是抽象出的指令集,由原生語言編譯而成,作為虛擬機(jī)執(zhí)行階段的輸入。很多語言都實(shí)現(xiàn)了自己的虛擬機(jī),比如Java、C#和Lua。PHP語言也有自己的虛擬機(jī),稱為Zend虛擬機(jī)

PHP7完成基本的準(zhǔn)備工作后,會(huì)啟動(dòng)Zend引擎,加載注冊(cè)的擴(kuò)展模塊,然后讀取對(duì)應(yīng)的腳本文件,Zend引擎會(huì)對(duì)文件進(jìn)行詞法和語法分析,生成抽象語法樹,接著抽象語法樹被編譯成Opcodes,如果開啟了Opcache,編譯的環(huán)節(jié)會(huì)被跳過從Opcache中直接讀取Opcodes進(jìn)行執(zhí)行。

PHP7中詞法語法分析,生成抽象語法樹,然后編譯成Opcodes及被執(zhí)行均由Zend虛擬機(jī)完成。這里將詳細(xì)闡述抽象語法樹編譯成Opcodes的過程,以及Opcodes被執(zhí)行的過程,來闡述Zend虛擬機(jī)的實(shí)現(xiàn)原理及關(guān)鍵的數(shù)據(jù)結(jié)構(gòu)。

1 基礎(chǔ)知識(shí)

Zend虛擬機(jī)(稱為Zend VM)是PHP語言的核心,承擔(dān)了語法詞法解析、抽象語法樹編譯以及指令的執(zhí)行工作,下面我們討論一下Zend虛擬機(jī)的基礎(chǔ)架構(gòu)以及相關(guān)的基礎(chǔ)知識(shí)。

1.1 Zend虛擬機(jī)架構(gòu)

Zend虛擬機(jī)主要分為解釋層、中間數(shù)據(jù)層和執(zhí)行層,下面給出各層包含的內(nèi)容,如圖1所示。


圖1 Zend虛擬機(jī)架構(gòu)圖

下面解釋下各層的作用。

(1)解釋層

這一層主要負(fù)責(zé)把PHP代碼進(jìn)行詞法和語法分析,生成對(duì)應(yīng)的抽象語法樹;另一個(gè)工作就是把抽象語法樹進(jìn)行編譯,生成符號(hào)表和指令集;

(2)中間數(shù)據(jù)層

這一層主要包含了虛擬機(jī)的核心部分,執(zhí)行棧的維護(hù),指令集和符號(hào)表的存儲(chǔ),而這三個(gè)是執(zhí)行引擎調(diào)度執(zhí)行的基礎(chǔ);

(3)執(zhí)行層

這一層是執(zhí)行指令集的引擎,這一層是最終的執(zhí)行并生成結(jié)果,這一層里面實(shí)現(xiàn)了大量的底層函數(shù)。

為了更好地理解Zend虛擬機(jī)各層的工作,我們先了解一下物理機(jī)的一些基礎(chǔ)知識(shí),讀者可以對(duì)照理解虛擬機(jī)的原理。

1.2 符號(hào)表

符號(hào)表是在編譯過程中,編譯程序用來記錄源程序中各種名字的特性信息,所以也稱為名字特性表。名字一般包含程序名、過程名、函數(shù)名、用戶定義類型名、變量名、常量名、枚舉值名、標(biāo)號(hào)名等。特性信息指的是名字的種類、類型、維數(shù)、參數(shù)個(gè)數(shù)、數(shù)值及目標(biāo)地址(存儲(chǔ)單元地址)等。

符號(hào)表有什么作用呢?一是協(xié)助進(jìn)行語義檢查,比如檢查一個(gè)名字的引用和之前的聲明是否相符,二是協(xié)助中間代碼生成,最重要的是在目標(biāo)代碼生成階段,當(dāng)需要為名字分配地址時(shí),符號(hào)表中的信息將是地址分配的主要依據(jù)。


圖2 符號(hào)表創(chuàng)建示例

符號(hào)表一般有三種構(gòu)造和處理方法,分別是線性查找,二叉樹和Hash技術(shù),其中線性查找法是最簡單的,按照符號(hào)出現(xiàn)的順序填表,每次查找從第一個(gè)開始順序查找,效率比較低;二叉樹實(shí)現(xiàn)了對(duì)折查找,在一定程度上提高了效率;效率最高的是通過Hash技術(shù)實(shí)現(xiàn)符號(hào)表,相信大家對(duì)Hash技術(shù)有一定的了解,而PHP7中符號(hào)表就是使用的HashTable實(shí)現(xiàn)的。

1.3 函數(shù)調(diào)用棧

為了更清晰地了解虛擬機(jī)中函數(shù)調(diào)用的過程,我們先了解一下物理機(jī)的簡單原理,主要涉及函數(shù)調(diào)用棧的概念,而Zend虛擬機(jī)參照了物理機(jī)的基本原理,做了類似的設(shè)計(jì)。

下面以一段C代碼描述一下系統(tǒng)棧和函數(shù)過程調(diào)用,代碼如下:

  int funcB(int argB1, int argB2)

  {

         int varB1, varB2;

         return argB1+argB2;

  }

  int funcA(int argA1, int argA2)

  {

       int varA1, varA2;

        return argA1+argA2+funcB( 3, 4);

  }    

  int main()

  {

      int varMain;

      return funcA(1, 2);

     }

這段代碼運(yùn)行時(shí),首先main函數(shù)會(huì)壓棧, 首先將局部變量varMain入棧,main函數(shù)調(diào)用了funcA函數(shù),C語言會(huì)從后往前,將函數(shù)參數(shù)壓棧,先壓第二個(gè)參數(shù)argA2=2,再壓第一個(gè)參數(shù)argA1=1,同時(shí)對(duì)于funcA的返回會(huì)產(chǎn)生一個(gè)臨時(shí)變量等待賦值,也會(huì)被壓棧,這些稱為main函數(shù)的棧幀;接著將funcA壓棧,同樣的先將局部變量varA1和varA2壓入棧中,因?yàn)檎{(diào)用了函數(shù)funcB,會(huì)將參數(shù)argB2=4和argB1=3壓入棧中,同時(shí)把funcB的返回產(chǎn)生的臨時(shí)變量壓入棧中,這部分稱為funcA的棧幀;同樣,funcB被壓入棧中,如圖3所示。


圖3 函數(shù)調(diào)用壓棧過程示意圖

funcB函數(shù)執(zhí)行,對(duì)argB1和argB2進(jìn)行相加操作,執(zhí)行后得到返回值為7,然后funcB的棧幀出棧,funcA中臨時(shí)變量TempB被賦值為7,繼而進(jìn)行相加操作,得到結(jié)果為10,然后funcA出棧,main函數(shù)中臨時(shí)變量TempA被賦值為10,最終main函數(shù)返回并出棧,整個(gè)函數(shù)調(diào)用結(jié)束。如圖4所示。


圖4 函數(shù)調(diào)用出棧過程示意圖

1.4 指令

匯編語句中的指令語句一般格式為:

     [標(biāo)號(hào):]     [前綴]  指令助記符    [操作數(shù)]     [;注釋]

其中:

1)標(biāo)識(shí)符字段由各種有效字符組成,一般表示符號(hào)地址,具有段基址、偏移量、類型三種屬性。通常情況下這部分是可選部分,主要為便于程序的讀寫方便而使用。

2)助記符,規(guī)定指令或偽指令的操作功能,是語句中唯一不可缺少的部分。對(duì)于指令,匯編程序會(huì)將其翻譯成機(jī)器語言指令:

MOV   AX, 100  →   B8 00 01

3)操作數(shù),指令語句中提供給指令的操作對(duì)象、存放位置。操作數(shù)可以是1個(gè)、2個(gè)或0個(gè),2個(gè)時(shí)用逗號(hào)‘,’分開。比如“RET;”對(duì)應(yīng)的操作數(shù)個(gè)數(shù)是0個(gè),“INC BX;”對(duì)應(yīng)的操作數(shù)個(gè)數(shù)是1,“MOV AX,DATA;”對(duì)應(yīng)的操作數(shù)個(gè)數(shù)是2個(gè)。

4)注釋,以“ ;”開始,給以編程說明。

符號(hào)表、函數(shù)調(diào)用棧以及指令基本構(gòu)成了物理機(jī)執(zhí)行的基本元素,Zend虛擬機(jī)也同樣實(shí)現(xiàn)了符號(hào)表,函數(shù)調(diào)用棧及指令,來運(yùn)行PHP代碼,下面我先討論一下Zend虛擬機(jī)相關(guān)的數(shù)據(jù)結(jié)構(gòu)。

2相關(guān)數(shù)據(jù)結(jié)構(gòu)

Zend虛擬機(jī)包含了詞法語法分析,抽象語法樹的編譯,以及Opcodes的執(zhí)行,本文主要詳細(xì)介紹抽象語法樹和Opcodes的執(zhí)行過程,在展開介紹之前,先闡述一下用到的基本的數(shù)據(jù)結(jié)構(gòu),為后面原理性的介紹奠定基礎(chǔ)。

2.1 EG(v)
首先介紹的是全局變量executor_globals,EG(v)是對(duì)應(yīng)的取值宏,executor_globals對(duì)應(yīng)的是結(jié)構(gòu)體_zend_executor_globals,是PHP生命周期中非常核心的數(shù)據(jù)結(jié)構(gòu)。這個(gè)結(jié)構(gòu)體中維護(hù)了符號(hào)表(symbol_table, function_table,class_table等),執(zhí)行棧(zend_vm_stack)以及包含執(zhí)行指令的zend_execute_data,另外還包含了include的文件列表,autoload函數(shù),異常處理handler等重要信息,下面給出_zend_executor_globals的結(jié)構(gòu)圖,然后分別闡述其含義,如圖5所示。


圖5 EG(v)結(jié)構(gòu)圖

這個(gè)結(jié)構(gòu)體比較復(fù)雜,下面我們介紹幾個(gè)核心的成員。

1)symbol_table:符號(hào)表,這里面主要是存的全局變量,以及一些魔術(shù)變量,比如$_GET、$_POST等;

2)function_table:函數(shù)表,主要存放函數(shù),包括大量的內(nèi)部函數(shù),以及用戶自定義的函數(shù),比如zend_version,func_num_args,str系列函數(shù),等等;

3)class_table:類表,主要存放內(nèi)置的類以及用戶自定義的類,比如stdclass、throwable、exception等類;

4)zend_constants:常量表,存放PHP中的常量,比如E_ERROR、E_WARNING等;

5)vm_stack:虛擬機(jī)的棧,執(zhí)行時(shí)壓棧出棧都在這上面操作;

6)current_execute_data:對(duì)應(yīng)_zend_execute_data結(jié)構(gòu)體,存放執(zhí)行時(shí)的數(shù)據(jù)。

下面針對(duì)于符號(hào)表、指令集、執(zhí)行數(shù)據(jù)和執(zhí)行棧進(jìn)行詳細(xì)介紹。

2.2 符號(hào)表

PHP7中符號(hào)表分為了symbol_table、function_table和class_table等。

(1)symbol_table

symbol_table里面存放了變量信息,其類型是HashTable,下面我們看一下具體的定義:

       //符號(hào)表緩存

       zend_array *symtable_cache[SYMTABLE_CACHE_SIZE];

      zend_array **symtable_cache_limit;

      zend_array **symtable_cache_ptr;

       //符號(hào)表

       zend_array symbol_table;

symbol_table里面有什么呢,代碼”$a=1;”對(duì)應(yīng)的symnol_table,如圖6所示。


圖6 symbol_table示意圖

從圖6中可以看出,符號(hào)表中有我們常見的超全局變量$_GET、$_POST等,還有全局變量$a。在編譯過程中會(huì)調(diào)用zend_attach_symbol_table函數(shù)將變量加入symbol_table中。

(2)function_table

function_table對(duì)應(yīng)的是函數(shù)表,其類型也是HashTable,見代碼:

       HashTable *function_table;  /* function symbol table */

函數(shù)表中存儲(chǔ)哪些函數(shù)呢?同樣以上述代碼為例,我們利用GDB印一下function_table的內(nèi)容:

(gdb) p *executor_globals.function_table

$1 = {gc = {refcount = 1, u = {v = {type = 7 "a", flags = 0 "