PHP內核探索:操作碼OpCode

運行一段PHP代碼主要有兩個階段:編譯和執行。 當然編譯過程中還包括詞法分析語法分析不同階段和細節,這裏我們將其作爲一個整體。在這兩個階段之間,PHP代碼會被編譯成op code,可以將其認爲是引擎的一箇中間語言,編輯階段把PHP源碼生成op code,然後在執行階段執行這些op code。這篇文章將簡單的介紹op code。

PHP代碼編譯之後會生成許多的op,每一個op都是一個zend_op類型的c變量。相關的定義可以在{PHPSRC}/Zend/zend_compile.h中看到:

01 struct _zend_op { 
02     opcode_handler_t handler; 
03     znode result; 
04     znode op1; 
05     znode op2; 
06     ulong extended_value; 
07     uint lineno; 
08     zend_uchar opcode; 
09 }; 
10    
11 typedef struct _zend_op zend_op; 

簡單的說說這幾個字段:

1. result,op1,op2

這三個字段都是znode類型,它們是op的操作數和操作結果載體,當然並不是每個op都需要使用這三個字段,根據op的功能不同,會使用其中某些字段。比如類型爲ZEND_ECHO的op值需要使用op1,功能就是將op1中的相應的值輸出。一會再單獨介紹znode類型。

2. opcode

opcode的類型爲zend_uchar,zend_uchar實際上就是unsigned char,此字段保存的整形值即爲op的編號,用來區分不同的op類型,opcode的可取值都被定義成了宏,可以在{PHPSRC}/Zend/zend_vm_opcodes.h中看到這些宏的定義,類似如下:

01 #define ZEND_NOP                               0 
02 #define ZEND_ADD                               1 
03 #define ZEND_SUB                               2 
04 #define ZEND_MUL                               3 
05 #define ZEND_DIV                               4 
06 #define ZEND_MOD                               5 
07 #define ZEND_SL                                6 
08 #define ZEND_SR                                7 
09 #define ZEND_CONCAT                            8 
10 #define ZEND_BW_OR                             9 
11 #define ZEND_BW_AND                           10 
12 //...... 

3. handler

op的執行句柄,其類型爲opcode_handler_t,opcode_handler_t的類型定義爲typedef int (ZEND_FASTCALL *opcode_handler_t) (ZEND_OPCODE_HANDLER_ARGS); 這個函數指針爲op定義了執行方式,每一種opcode字段都對應一個種類的handler,比如opcode= 38 (ZEND_ASSIGN), 那麼其對應的handler對應的就是static int ZEND_FASTCALL  ZEND_ASSIGN_**種類的handler,根據op操作數類型的不同,可以確定到這個種類中的某一個具體的函數,比如如果$a = 1;這樣的代碼生成的op,操作數爲const和cv,最後就能確定handler爲函數ZEND_ASSIGN_SPEC_CV_CONST_HANDLER,這些handler函數都定義在{PHPSRC}/Zend/zend_vm_execute.h中,此文件可以由一個PHP腳本生成,其中也定義了通過op來映射得到其hander的算法。

4. lineno

op對應源代碼文件中的行號。

5. extended_value

擴展字段暫時不介紹

操作數znode簡介 

操作數字段是這個類型中比較重要的部分了,其中op1,op2,result三個操作數定義爲znode類型,znode相關定義在此文件中:

01 typedef struct _znode { 
02     int op_type; 
03     union
04         zval constant; 
05    
06         zend_uint var; 
07         zend_uint opline_num; /*  Needs to be signed */ 
08         zend_op_array *op_array; 
09         zend_op *jmp_addr; 
10         struct
11             zend_uint var;  /* dummy */ 
12             zend_uint type; 
13         } EA; 
14     } u; 
15 } znode; 

znode類型中定義了兩個字段:

1. op_type

這個int類型的字段定義znode操作數的類型,這些類型的可取值的宏定義在此文件中

1 #define IS_CONST    (1<<0) 
2 #define IS_TMP_VAR  (1<<1) 
3 #define IS_VAR      (1<<2) 
4 #define IS_UNUSED   (1<<3)    /* Unused variable */ 
5 #define IS_CV       (1<<4)    /* Compiled variable */ 
  • IS_CONST:表示常量,例如$a = 123; $b = "hello";這些代碼生成OP後,123和"hello"都是以常量類型操作數存在。
  • IS_TMP_VAR:表示臨時變量,臨時變量一般在前面加~來表示,這是一些OP執行過程中需要用到的中間變量,例如初始化一個數組的時候,就需要一個臨時變量來暫時存儲數組zval,然後將數組賦值給變量。
  • IS_VAR: 一般意義上的變量,以$開發表示,此種變量本人目前研究的較少,暫不介紹
  • IS_UNUSED : 暫時不介紹,從名字來看應該是標識爲不使用
  • IS_CV:這種類型的操作數比較重要,此類型是在PHP後來的版本中(大概5.1)中才出現,CV的意思是compiled variable,即編譯後的變量,變量都是保存在一個符號表中,這個符號表是一個哈希表,試想如果每次讀寫變量的時候都需要到哈希表中去檢索,勢必會對效率有一定的影響,因此在執行上下文環境中,會將一些編譯期間生成的變量緩存起來,此過程以後再詳細介紹。此類型操作數一般以!開頭表示,比如變量$a=123;$b="hello"這段代碼,$a和$b對應的操作數可能就是!0和!1, 0和1相當於一個索引號,通過索引號從緩存中取得相應的值。

2. u

此字段爲一個聯合體,根據op_type的不同,u取不同的值。比如op_type=IS_CONST的時候,u中的constant保存的就是操作數對應的zval結構。例如$a=123時,123這個操作數中,u中的constant是一個IS_LONG類型的zval,其值lval爲123。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章