PHP內核探索 —— PHP腳本的執(zhí)行細節(jié):PHP、C、匯編、機器碼
眾所周知,計算機的CPU只能執(zhí)行二進制的機器碼,每種CPU都有對應的匯編語言,匯編語言編譯器將匯編語言翻譯成二進制的機器語言,然后CPU開始執(zhí)行這些機器碼。匯編語言作為機器語言與程序設計者之間的一個層,給我們帶來了很多方便,程序員不需要用晦澀的01數(shù)字來書寫程序,當然人們并不滿足這樣的一個進步,于是在匯編語言之上又多了一個層——C語言,C語言更貼近人類熟悉的“自然語言”,程序設計者可以通過C語言編譯器將C源代碼文件編譯成目標文件(二進制文件,中間會先翻譯成匯編語言,然后由匯編語言生成機器碼),然后將各個目標文件連接在一起就組成了一個可執(zhí)行文件。正如有人說過的一句名言“計算機科學領域的任何問題都可以通過增加一個間接的中間層來解決”(“Any problem in computer science can be solved by another layer of indirection.”) PHP語言就是在C語言之上的一個層,PHP引擎是由C語言來實現(xiàn)的,因此PHP語言這一個在C之上抽象出來的層使用起來比C更簡單方便,入門門檻更低。
那么,PHP語言究竟如何被執(zhí)行呢?
PHP語言到C語言之間的轉換如果使用“翻譯”這個詞是不夠準確的,因為引擎不是將PHP語言轉換成C語言,然后將轉換后的C語言編譯鏈接執(zhí)行。引擎在解析PHP代碼的時候通常是分為兩個部分,編譯和執(zhí)行:
編譯階段:引擎把PHP代碼轉換成opcode中間代碼執(zhí)行階段:引擎解釋并執(zhí)行編譯階段產生的opcode關于op code會有專門的文章來介紹,現(xiàn)在網絡上也已經有很多相關內容的文章,總之PHP代碼會被編譯成_zend_op_array的形式,這是一個結構體,其中包括很多相關屬性,以及最重要的成員zend_op *opcodes,即opcode的數(shù)組。執(zhí)行階段引擎會按照順序執(zhí)行各個opcode。
目前5.3.2版本的PHP中,opcode一共有154種,可以在{PHPSRC}/Zend/zend_vm_opcodes.h看到這些opcode的宏定義。op的結構定義為:
struct _zend_op {opcode_handler_t handler;znode result;znode op1;znode op2;ulong extended_value;uint lineno;zend_uchar opcode;};
其中的成員opcode就對應154個opcode宏定義中的一個,每一個op根據opcode和操作數(shù)的類型不同都會對應一個相關的執(zhí)行句柄(opcode_handler_t handler),執(zhí)行句柄是一個函數(shù)指針,op的執(zhí)行執(zhí)行句柄都定義在{PHPSRC}/Zend/zend_vm_execute.h中,這個文件可以通過一個PHP腳本({PHPSRC}/Zend/zend_vm_gen.php)來生成,這個PHP腳本用來生成zend_vm_opcodes.h和zend_vm_execute.h兩個文件,zend_vm_execute.h的內容會根據生成時的參數(shù)不同而不同,這里主要是可以定置zend 引擎對op的分發(fā)方式,比如用CALL,SWITCH,GOTO,默認的是用CALL,也就是函數(shù)調用,所以這里就以函數(shù)調用來簡單的介紹下這個文件的功能(文件極大,有近36000行,所以不要仔細啃),在這個文件中所有定義為 static int ZEND_FASTCALL 并且以 ZEND_* 開頭的函數(shù)就是op的句柄,此文件中第一個函數(shù)execute是執(zhí)行op的主方法,以這里作為入口執(zhí)行一連串的op。可以說整個PHP的功能特性都是通過這些op句柄完成的(當然這些句柄會間接調用其他模塊中的功能),那么這154個opcode如何對應到這些static int ZEND_FASTCALL? ZEND_*的執(zhí)行句柄的呢?同樣在這個文件中,可以看到zend_init_opcodes_handlers函數(shù),這個函數(shù)初始化一個 static const opcode_handler_t labels[]數(shù)組,這個 labels數(shù)組就是handlers的一張表,這個表有近4000個項,有一個算法將一個opcode映射到這個表中的一個元素,算法同樣在zend_vm_execute.h中可以找到,靠近文件結尾zend_vm_set_opcode_handler和zend_vm_get_opcode_handler就是這個算法的實現(xiàn)。
那么引擎是如何通過這些op handler實現(xiàn)PHP語言的特性的呢?這里我舉一個最簡單的例子,考慮下面只有一行的PHP代碼:
<?php$a = 123;?>
通過某種方法(以后再介紹這些方法)我們可以知道這行代碼主要生成一個zend_op,其主要成員值為:
opcode = 38? (對應#define ZEND_ASSIGN??38)op1?????? = $a ($a變量實際上是以cv形式存在,以后介紹)op2?????? = 123 (以const常量形式存在)handler = ZEND_ASSIGN_SPEC_CV_CONST_HANDLER(得到這個handler的名字不是一件容易的事,以后給出方法)
opcode ZEND_ASSIGN的意思是將一個常量賦值給一個cv(compiled variable),這個cv其實就是$a變量的一種存在形式。在zend_vm_execute.h中搜索到ZEND_ASSIGN_SPEC_CV_CONST_HANDLER的定義,其主要功能就是取op2的值123,將其賦值給op1的變量,當然這個過程比想象中的要復雜一些,會有變量的初始化,變量的寫時賦值等過程,以后會介紹每一個過程。這樣這條PHP語句的功能就完成了。可以看出,op handler只是按照一些固定的方式來對操作數(shù)op1 op2(可能還有result)進行操作,handler不理會這些操作數(shù)中的具體值,這些值是在編譯階段生成op的時候確定的,比如如果$a = 123 改成 $a =456,那么生成的op中op2就是456了,handler始終按照固定的方式來處理。
因此我們能知道,PHP的執(zhí)行過程是先通過編譯器將PHP代碼編譯成op code,然后然后zend虛擬機按照一定順序執(zhí)行這些opcode,具體是將每個opcode分發(fā)給特定的op code handler。
相關文章:
1. CSS3中Transition屬性詳解以及示例分享2. ASP基礎入門第八篇(ASP內建對象Application和Session)3. jsp文件下載功能實現(xiàn)代碼4. XMLHTTP資料5. asp.net core項目授權流程詳解6. html中的form不提交(排除)某些input 原創(chuàng)7. ASP常用日期格式化函數(shù) FormatDate()8. CSS3實現(xiàn)動態(tài)翻牌效果 仿百度貼吧3D翻牌一次動畫特效9. ASP動態(tài)網頁制作技術經驗分享10. 在JSP中使用formatNumber控制要顯示的小數(shù)位數(shù)方法
