国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > PHP > 正文

PHP-Zend引擎剖析之Hello World(二)

2019-11-06 06:11:52
字體:
來源:轉載
供稿:網友

前言

這一次,我圍繞Hello World來展開Zend虛擬機的執行過程。Hello World的php版本:
<?php     echo 'Hello World';?>
前一篇文章聊到的詞法分析階段就會把上邊的腳本分析出一個Token序列:我們得到一個Token序列:T_OPEN_TAG, T_ECHO, T_CONSTANT_ENCAPSED_STRING, ';', T_CLOSE_TAG。但在Zend虛擬機執行的過程中,是怎么去分析這個Token序列的?

跟蹤運行軌跡

我們還是從命令行入手,在$PHPSRC/sapi/cli/php_cli.c中的do_cli函數里邊接收了命令行的參數輸入(php -f HelloWorld.php表示執行HelloWorld.php文件)。我們追蹤到$PHPSRC/main/main.c里邊有php_execute_script的定義,緊接著調用了zend_execute_scripts() <Zend/Zend.c>,在zend_execute_scripts的定義里邊我們發現了:
 EG(active_op_array) = zend_compile_file(file_handle, type TSRMLS_CC);zend_execute(EG(active_op_array) TSRMLS_CC);
首先通過zend_compile_file把文件解析成opcode中間代碼(這一步會經過詞法語法分析),然后用zend_execute執行這個生成的中間代碼(這里就是所謂的運行時)。這里很像C語言的編譯方式,先編譯成匯編,然后再轉成機器碼,這里的opcode就類似C語言編譯過程中生成的匯編。還可以延伸出一個思路,因為每次解析PHP文件時,都需要經過詞法語法分析得到對應的opcode,其實在腳本文件不變化的時候,生成的opcode也不需要變化,因此為了減少PHP腳本的執行時間,可以把腳本的opcode緩存起來(例如緩存在共享內存里邊)。我給出一個流程圖,然后隨著這個流程圖,看看Zend做了些什么事情:我們先看看如何編譯出opcode的。

詞法語法分析->opcode

從上節知道我們通過zend_compile_file(實際上為compile_file()<定義在Zend/zend_language_scanner.c的555行>)把腳本文件編譯出opcode,實際上通過zendparse這個API來編譯出opcode的。PHP的語法解析器是用bison來生成,安裝完之后在$PHPSRC/Zend目錄運行:
bison -o zend_language_parser.c zend_language_parser.y
在Zend目錄下就會生成語法解析器zend_language_parser.c。而這里的zendparse就是語法解析器里邊的yyparse!我們忽略掉生成的語法解析器,就Hello World的例子來跟蹤一下bison的聲明文件(我去掉不想關的聲明):
start:top_statement_list     { zend_do_end_compilation(TSRMLS_C); };top_statement_list:top_statement_list  { zend_do_extended_info(TSRMLS_C); } top_statement { HANDLE_INTERACTIVE(); }|     /* empty */;top_statement:statement                              { zend_verify_namespace(TSRMLS_C); };statement:unticked_statement { DO_TICKS(); }|     T_STRING ':' { zend_do_label(&$1 TSRMLS_CC); };unticked_statement:|     T_ECHO echo_ex語法分析從start開始,自上而下的分析,一個PHP腳本就是對應一個top_statement_list,接著分成每一行一條語句statement,發現echo 'Hello World'是一條unticked_statement(留意一下echo_expr_list的聲明, 我們還可以發現語法上是支持echo 'Hello', ' World'的)。最后遞歸到T_CONSTANT_ENCAPSED_STRING狀態就結束了這一行的語法解析。在這里我們忽略掉編譯原理在語法分析階段是怎么去做回溯等等東西,我們關注一下Zend引擎自身的的問題。在規則后邊的塊"{}"里邊的代碼就是用來處理掃描到此規則時的動作,可以看到echo的執行是調用了zend_do_echo函數的。在動作聲明的塊里邊我們看到了$$, $1,$2,$3等,這些對應的就是該條規則里邊的返回值,參數1,參數2……,這里的返回值以及參數都是YYSTYPE類型,這個類型在43行里邊有定義:#define YYSTYPE znode。znode的定義在zend_compile.h里邊:留意到zend_op這個結構,于是跟蹤發現這個就是最后每條語句對應的opcode結構了!!!!opcode的結構跟匯編有很大的相似之處,一個操作符,兩個操作數。在Zend引擎中,每個opcode主要的東西就是那個handler,一會我們會看到Zend里邊是怎么生成這個handler的。到了這里先Hold住一下,回過頭,我們看一下Hello World這個例子生成的opcode是什么。裝上vld,然后運行:php -dvld.active=1 HelloWorld.php,我們就可以看到這個PHP文件編譯出來的opcode列表了:可以看到echo這個語句的opcode類型是ECHO,同時return沒有返回值,只有一個操作數"Hello World"。現在經過了語法分析,我們對每條語句都編譯出了opcode,Zend就會把它放入一個op_array里邊(其實就是一個opcode的列表)。回過頭我們看一下zend_do_echo做了什么事情:首先通過get_next_op在當前的op_array的最后邊生成一條opcode,然后設置其opcode類型是ZEND_ECHO,然后設置它的第一個參數op1,同時標記第二個參數op2是不需要使用的(unused的)。經過了這么多步驟之后我們得到了一個op_array的列表,這個列表里邊的每一條opcode都綁定了自己的類型,接著我們看一下每個opcode節點是如何綁定handler的。zend_vm_def.h定義了ZEND_ECHO的handler,留意到這里的40,一會需要用到,因為echo的參數可以有幾種:常量,變量等等,所以對應著不同的handler在zend_vm_execute.h定義了opcode對應的所有的handler,我們只關注echo相關的handler,注意到其中的代碼:
void zend_init_opcodes_handlers(void){static const opcode_handler_t labels[] = {//40913行ZEND_ECHO_SPEC_CONST_HANDLER,//41914行ZEND_ECHO_SPEC_CONST_HANDLER,ZEND_ECHO_SPEC_CONST_HANDLER,ZEND_ECHO_SPEC_CONST_HANDLER,ZEND_ECHO_SPEC_CONST_HANDLER};
請花費短暫的時間先記住這里的labels以及行數。發現了獲取handler的方法最后邊return語句的計算,根據前面說的echo的opcode是40(假設兩個參數op1,op2的type都是0),于是乎其對應的handler就是:zend_opcode_handlers[40*25+0*5+0*5] = zend_opcode_handlers[1000] = labels[1000] = ZEND_ECHO_SPEC_CONST_HANDLER(怎么來的?因為:41914行-40913行-1=1000)。

虛擬機執行opcode

前邊我們已經解釋了zend_compile_file把一個腳本編譯成一個opcode的list:
 EG(active_op_array) = zend_compile_file(file_handle, type TSRMLS_CC);zend_execute(EG(active_op_array) TSRMLS_CC);
在這之后,Zend引擎用zend_execute執行返回的opcode。我們定位到了zend_execute最后執行到Zend/zend_vm_execute.h的337行:可以看到,虛擬機執行的時候會循環當前的opcode列表,然后調用每一行opcode的handler,根據handler返回值確定下一步做啥(例如函數調用等,以后再展開)。在這篇文章中我們只關注跟Hello World相關的東西,我們前邊知道echo的handler是ZEND_ECHO_SPEC_CONST_HANDLER,通過最后的定位你會發現其調用了:
zend_write = (zend_write_func_t) utility_functions->write_function;
這里的utility_functions里邊包含了一些基礎的handler,每個sapi接入層自己修改了這里的基礎函數指針,例如在命令行模式下,最后調用到了sapi_cli_single_write:
源碼中,我們看到了最后的寫操作就是調用了write/fwrite寫入到標準輸出流(也即是終端屏幕上)。

結語

最后根據前邊的過程,再展開一下流程圖就是:
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 松潘县| 新郑市| 依兰县| 寿光市| 蓬莱市| 德令哈市| 龙海市| 大方县| 丰台区| 玉门市| 剑川县| 邻水| 读书| 灵丘县| 台北县| 罗山县| 大新县| 绥芬河市| 瑞安市| 黄浦区| 黎平县| 普安县| 新源县| 茶陵县| 蕲春县| 安乡县| 盐山县| 海南省| 大连市| 右玉县| 靖边县| 广水市| 连江县| 易门县| 大姚县| 谢通门县| 驻马店市| 镇远县| 犍为县| 蒙自县| 探索|