1. 程式人生 > >php原始碼之路第七章第一節 ( Zend虛擬機器)

php原始碼之路第七章第一節 ( Zend虛擬機器)

在前面的章節中,我們瞭解到一個PHP檔案在伺服器端的執行過程包括以下兩個大的過程:

    遞給php程式需要執行的檔案, php程式完成基本的準備工作後啟動PHP及Zend引擎, 載入註冊的擴充套件模組。 

    初始化完成後讀取指令碼檔案,Zend引擎對指令碼檔案進行詞法分析,語法分析。然後編譯成opcode執行。如過安裝了apc之類的opcode快取, 編譯環節可能會被跳過而直接從快取中讀取opcode執行。 

    在第二步中,詞法分析、語法分析,編譯中間程式碼,執行中間程式碼等各個部分統稱為Zend虛擬機器。與Java、C#等編譯型語言相比,PHP少了一個手動編譯的過程,它們無需編譯即可執行,我們稱其為解釋性語言。 Java有自己的Java虛擬機器,它在多個平臺上實現統一語言; C#有自己的.NET虛擬機器,它在單一平臺實現多種語言; PHP跟他們一樣,也有屬於自己的Zend虛擬機器。它們在本質是相同的,它們都是抽象的計算機。這些虛擬機器都是在某種較底層的語言上抽象出另外一種語言,有自己的指令集,有自己的記憶體管理體系。它們最終都會將抽象級別較高的語言實現轉化為抽象級別較低的語言實現,並且實現其它輔助功能,如記憶體管理,垃圾回收等機制,以減少程式設計師在具體實現上的工作,從而可以將更多的時間和精力投入到業務邏輯中。從抽象層次看,Zend虛擬機器比Java等語言更高階一些,這裡的高階不是說功能更強大或效率更高,簡單點說,Zend虛擬機器離真正的機器實現更遠一些。最近這些年,語言的發展只是不斷的抽象,不斷的遠離機器,沒有根本性的變化。

本章,我們從虛擬機器的前世今生講起,敘述Zend虛擬機器的實現原理,關鍵的資料結構,並其中穿插一個關於語法實現的示例和原始碼加密解密的過程說明。

在wiki中虛擬機器的定義是:虛擬機器(Virtual Machine),在電腦科學中的體系結構裡,是指一種特殊的軟體,他可以在計算機平臺和終端使用者之間建立一種環境,而終端使用者則是基於這個軟體所建立的環境來操作軟體。在電腦科學中,虛擬機器是指可以像真實機器一樣執行程式的計算機的軟體實現。

虛擬機器是一種抽象的計算機,它有自己的指令集,有自己的記憶體管理體系。在此類虛擬機器上實現的語言比較低抽象層次的語言更加明瞭,更加簡單易學。

虛擬機器的型別
虛擬機器是一種抽象的計算機,是對真實計算機的虛擬和模擬,現在的計算機有不同的指令集架構(ISA: Instruction Set Architecture), ISA是處理的一個部分,不同的處理器會有不同的架構,最常見的有3種:

    基於棧的Stack Machines: 運算元儲存在棧上。而不是使用暫存器來儲存,現在很少有真實機器採用這個模型。對於虛擬機器來說因為指令空間佔用少,並且實現簡單,很多虛擬機器採用這種模型,比如:JVM,HHVM等。

    基於累加器的Accumulator Machines。這個模型使用稱作累加器(Accumulator)的的暫存器來儲存一個運算元以及操作的結果。
    基於通用暫存器的General-Purpose-Register Machines,這些暫存器沒有特殊的用途。編譯器可以將運算元儲存在這些暫存器中。ZendVM採用的就是基於暫存器的架構。 

Zend虛擬機器核心實現程式碼
為了方便讀者對Zend引擎的實現有個全面的感覺,下面列出涉及到Zend引擎實現的核心程式碼檔案功能參考。

Zend引擎的核心檔案都在$PHP_SRC/Zend/目錄下面。不過最為核心的檔案只有如下幾個:
 - PHP語法實現 
Zend/zend_language_scanner.l 
Zend/zend_language_parser.y 

Opcode編譯 
Zend/zend_compile.c 

執行引擎 
Zend/zend_vm_* 
Zend/zend_execute.c 
Zend虛擬機器體系結構
從概念層將Zend虛擬機器的實現進行抽象,我們可以將Zend虛擬機器的體系結構分為:解釋層、執行引擎、中間資料層,如圖所示:

這裡寫圖片描述

    當一段PHP程式碼進入Zend虛擬機器,它會被執行兩步操作:編譯和執行。對於一個解釋性語言來說,這是一個創造性的舉動,但是,現在的實現並不徹底。現在當PHP程式碼進入Zend虛擬機器後,它雖然會被執行這兩步操作,但是這兩步操作對於一個常規的執行過程來說卻是連續的,也就是說它並沒有轉變成和Java這種編譯型語言一樣:生成一箇中間檔案存放編譯後的結果。如果每次執行這樣的操作,對於PHP指令碼的效能來說是一個極大的損失。雖然有類似於APC,eAccelerator等快取解決方案。但是其本質上是沒有變化的,並且不能將兩個步驟分離,各自發展壯大。

解釋層
    解釋層是Zend虛擬機器執行編譯過程的位置。它包括詞法解析、語法解析和編譯生成中間程式碼三個部分。詞法分析就是將我們要執行的PHP原始檔,去掉空格,去掉註釋,切分為一個個的標記(token),並且處理程式的層級結構(hierarchical structure)。

    語法分析就是將接受的標記(token)序列,根據定義的語法規則,來執行一些動作,Zend虛擬機器現在使用的Bison使用巴科斯正規化(BNF)來描述語法。編譯生成中間程式碼是根據語法解析的結果對照Zend虛擬機制定的opcode生成中間程式碼,在PHP5.3.1中,Zend虛擬機器支援135條指令(見Zend/zend_vm_opcodes.h檔案),無論是簡單的輸出語句還是程式複雜的遞迴呼叫,Zend虛擬機器最終都會將所有我們編寫的PHP程式碼轉化成這135條指令的序列,之後在執行引擎中按順序執行。

中間資料層
    當Zend虛擬機器執行一個PHP程式碼時,它需要記憶體來儲存許多東西,比如,中間程式碼,PHP自帶的函式列表,使用者定義的函式列表,PHP自帶的類,使用者自定義的類,常量,程式建立的物件,傳遞給函式或方法的引數,返回值,區域性變數以及一些運算的中間結果等。我們把這些所有的存放資料的地方稱為中間資料層。

    如果PHP以mod擴充套件的方式依附於Apache2伺服器執行,中間資料層的部分資料可能會被多個執行緒共享,如果PHP自帶的函式列表等。如果只考慮單個程序的方式,當一個程序被建立時它就會被載入PHP自帶的各種函式列表,類列表,常量列表等。當解釋層將PHP程式碼編譯完成後,各種使用者自定義的函式,類或常量會新增到之前的列表中,只是這些函式在其自身的結構中某些欄位的賦值是不一樣的。

    當執行引擎執行生成的中間程式碼時,會在Zend虛擬機器的棧中新增一個新的執行中間資料結構(zend_execute_data),它包括當前執行過程的活動符號列表的快照、一些區域性變數等。

執行引擎
    Zend虛擬機器的執行引擎是一個非常簡單的實現,它只是依據中間程式碼序列(EX(opline)),一步一步呼叫對應的方法執行。在執行引擎中沒並有類似於PC暫存器一樣的變數存放下一條指令,當Zend虛擬機器執行到某條指令時,當它所有的任務都執行完了,這條指令會自己呼叫下一條指令,即將序列的指標向前移動一個位置,從而執行下一條指令,並且在最後執行return語句,如此反覆。這在本質上是一個函式巢狀呼叫。

    回到開頭的問題,PHP通過詞法分析、語法分析和中間程式碼生成三個步驟後,PHP檔案就會被解析成PHP的中間程式碼opcode。生成的中間程式碼與實際的PHP程式碼之間並沒有完全的一一對應關係。只是針對使用者所給的PHP程式碼和PHP的語法規則和一些內部約定生成中間程式碼,並且這些中間程式碼還需要依靠一些全域性變數中轉資料和關聯。至於生成的中間程式碼的執行過程是依據中間程式碼的順利,依賴於執行過程中的全域性變數,一步步執行。當然,在遇到一些函式跳轉也會發生偏移,但是最終還是會回到偏移點。