一直以來,橫觀國內的PHP現狀,很少有專門介紹PHP內部機制的書。呵呵,我會隨時記錄下研究的心得,有機會的時候,匯總成書。:)
今天這篇,我內心是想打算做為一個導論:
PHP是一個被廣泛應用的腳本語言,因為它的成功,所以很多時候,我們應用PHP的時候是更不不需要考慮底層到底是怎麼實現的。我相信大多數的PHP程序 員是不會去考慮這一點的。從我接觸PHP開始,到今天也就是3年,這三年裡,前倆年我一直都是在”用”PHP,每次寫出來一段腳本,我就會想“恩,不用擔 心,PHP解釋器會知道我想做什麼的”,直到去年來到雅虎,接受了一個工作,是做一個PHP的Extension,從這個時候開始,我就好奇於新接觸的一 大堆的新鮮事物,zend, TSRM, zval, hashtable, op_array…
於是我到處查閱資料,每次獲得一篇好的文章,或者一段好的文字我就會如獲珍寶,打印保存起來,細細研讀。我發現,國內關於PHP內部的資料真是少的可憐, 不知道是因為懂得的人多但是不願意分享,還是懂得的人本來就少,所以,這條路,我走的很辛苦。於是,就會有了這篇文章。
在這篇文章中,我會從整個PHP的執行期入手,大致的介紹下各個階段,詞法分析,語法分析,op code等等,以後的文章我會再詳細介紹每個階(當然,如果你急不可耐的想知道詳細,呵呵,那麼可以直接聯系我)。
從最初我們編寫的PHP腳本->到最後腳本被執行->得到執行結果,這個過程,其實可以分為如下幾個階段(鄙視:CSDN不能上圖):
首先,Zend Engine(ZE),調用詞法分析器(Lex生成的,源文件在 Zend/zend_language_sanner.l), 將我們要執行的PHP源文件,去掉空格 ,注釋,分割成一個一個的token。
然後,ZE會將得到的token forward給語法分析器(yacc生成, 源文件在 Zend/zend_language_parser.y),生成一個一個的op code,opcode一般會以op array的形式存在,它是PHP執行的中間語言。
最後,ZE調用zend_executor來執行op array,輸出結果。
那有什麼辦法可以看到我們的PHP腳本,最終被“翻譯”成什麼樣的呢? 也就是說,op code張的什麼樣子呢? 呵呵,達到這個,我們需要重新編譯PHP,修改它的compile_file和zend_execute函數。不過,在PECL中已經有這樣的模塊,可以 讓我們直接使用了,那就是由 Derick Rethans開發的VLD (Vulcan Logic Dissassembler)模塊。你只要下載這個模塊,並把他載入PHP中,就可以通過簡單的設置,來得到腳本翻譯的結果了。具體關於這個模塊的使用說 明-雅虎一下,你就知道^_^。
接下來,讓我們嘗試用VLD來查看一段簡單的PHP腳本的中間語言。
原始代碼:
<?PHP $i = “This is a string“;//I am commentsecho $i.‘ that has been echoed to screen‘;?>采用VLD得到的op codes:
filename:/home/Desktop/vldOutOne.PHP——————————————————————————————————————————-
function name: (null)number of ops: 7line # op fetch ext Operands2 0 FETCH_W local $0, ‘i‘1 ASSIGN $0, ‘This+is+a+string‘4 2 FETCH_R local $2, ‘i‘3 CONCAT ~3, $2,‘+that+has+been+echoed+to+screen‘ 4 ECHO ~36 5 RETURN 16 ZEND_HANDLE_EXCEPTION我們可以看到,源文件中的注釋,在op code中,已經沒有了,所以不用擔心注釋太多會影響你的腳本執行時間(實際上,它是會影響ZE的詞法處理階段的用時而已)。
現在我們來一條一條的分析這段op codes,每一條op code 又叫做一條op_line,都由如下7個部分,在zend_compile.h中,我們可以看到如下定義:
struct _zend_op { opcode_handler_t handler;znode result;znode op1;znode op2;ulong extended_value;uint lineno;zend_uchar opcode;};其中,opcode字段指明了這操作類型,handler指明了處理器,然後有倆個操作數,和一個操作結果。
可以看出,這個很類似於很多同學大學學習編譯原理時候的三元式,不同的是,這些中間代碼會被Zend VM(Zend虛擬機)直接執行。
真正負責執行的函數是,zend_execute, 查看zend_execute.h:
可以看出, zend_execute接受zend_op_array*作為參數。
可以看到,zend_op_array的結構和zend_function的結構很像(參看我的其他文章), 對於在全局作用域的代碼,就是不包含在任何function內的op_array,它的function_name為NULL。結構中的opcodes保存了屬於這個op_array的op code數組,zend_execute會從start_op開始,逐條解釋執行傳入的每條op code, 從而實現我們PHP腳本想要的結果。
下一次,我將介紹PHP變量的靈魂 – zval, 你將會看到PHP是如何實現它的變量傳遞,類型戲法,等等。