程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 網頁編程 >> PHP編程 >> PHP綜合 >> 深入淺出PHP(Exploring PHP)

深入淺出PHP(Exploring PHP)

編輯:PHP綜合

一直以來,橫觀國內的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(Exploring PHP)


圖1 處理流程
ZE是一個虛擬機,正是由於它的存在,所以才能使得我們寫PHP腳本,完全不需要考慮所在的操作系統類型是什麼。ZE是一個CISC(復雜指令處理器), 它支持150條指令(具體指令在 Zend/zend_vm_opcodes.h),包括從最簡單的ZEND_ECHO(echo)到復雜的 ZEND_INCLUDE_OR_EVAL(include,require),所有我們編寫的PHP都會最終被處理為這150條指令(op code)的序列,從而最終被執行。

那有什麼辦法可以看到我們的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指明了處理器,然後有倆個操作數,和一個操作結果。

  • FETCH_W, 是以寫的方式獲取一個變量,此處是獲取變量名”i”的變量於$0(*zval)。
  • 將字符串”this+is+a+string”賦值(ASSIGN)給$0
  • 字符串連接
  • 顯示

    可以看出,這個很類似於很多同學大學學習編譯原理時候的三元式,不同的是,這些中間代碼會被Zend VM(Zend虛擬機)直接執行。

    真正負責執行的函數是,zend_execute, 查看zend_execute.h:

  • ZEND_API extern void (*zend_execute)(zend_op_array *op_array TSRMLS_DC);

    可以看出, zend_execute接受zend_op_array*作為參數。

  • struct _zend_op_array {
  •     /* Common elements */
  •      zend_uchar type;
  •      char *function_name;
  •      zend_class_entry *scope;
  •      zend_uint fn_flags;
  •      union _zend_function *prototype;
  •      zend_uint num_args;
  •      zend_uint required_num_args;
  •      zend_arg_info *arg_info;
  •      zend_bool pass_rest_by_reference;
  •      unsigned char return_reference;
  •     /* END of common elements */
  •      zend_uint *refcount;
  •      zend_op *opcodes;
  •      zend_uint last, size;
  •      zend_compiled_variable *vars;
  •      int last_var, size_var;
  •      zend_uint T;
  •      zend_brk_cont_element *brk_cont_array;
  •      zend_uint last_brk_cont;
  •      zend_uint current_brk_cont;
  •      zend_try_catch_element *try_catch_array;
  •      int last_try_catch;
  •     /* static variables support */
  •      HashTable *static_variables;
  •      zend_op *start_op;
  •      int backpatch_count;
  •      zend_bool done_pass_two;
  •      zend_bool uses_this;
  •      char *filename;
  •      zend_uint line_start;
  •      zend_uint line_end;
  •      char *doc_comment;
  •      zend_uint doc_comment_len;
  •      void *reserved[ZEND_MAX_RESERVED_RESOURCES];
  • };

    可以看到,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是如何實現它的變量傳遞,類型戲法,等等。

    1. 上一頁:
    2. 下一頁:
    Copyright © 程式師世界 All Rights Reserved