程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 網頁編程 >> PHP編程 >> PHP綜合 >> PHP內核學習教程之php opcode內核實現

PHP內核學習教程之php opcode內核實現

編輯:PHP綜合

opcode是計算機指令中的一部分,用於指定要執行的操作, 指令的格式和規范由處理器的指令規范指定。 除了指令本身以外通常還有指令所需要的操作數,可能有的指令不需要顯式的操作數。 這些操作數可能是寄存器中的值,堆棧中的值,某塊內存的值或者IO端口中的值等等。

通常opcode還有另一種稱謂:字節碼(byte codes)。 例如Java虛擬機(JVM),.NET的通用中間語言(CIL: Common Intermeditate Language)等等。

1. Opcode簡介

opcode是計算機指令中的一部分,用於指定要執行的操作, 指令的格式和規范由處理器的指令規范指定。 除了指令本身以外通常還有指令所需要的操作數,可能有的指令不需要顯式的操作數。 這些操作數可能是寄存器中的值,堆棧中的值,某塊內存的值或者IO端口中的值等等

通常opcode還有另一種稱謂: 字節碼(byte codes)。 例如Java虛擬機(JVM),.NET的通用中間語言(CIL: Common Intermeditate Language)等等

PHP中的opcode則屬於前面介紹中的後著,PHP是構建在Zend虛擬機(Zend VM)之上的。PHP的opcode就是Zend虛擬機中的指令(基於Zend的中間代碼)

Relevant Link:

http://www.luocong.com/learningopcode/doc/1._%E4%BB%80%E4%B9%88%E6%98%AFOpCode%EF%BC%9F.htm

2. PHP中的Opcode

0x1: 數據結構

在PHP實現內部,opcode由如下的結構體表示

\php-5.6.17\Zend\zend_compile.h

struct _zend_op 
{
opcode_handler_t handler; // 執行該opcode時調用的處理函數
znode_op op1; // opcode所操作的操作數
znode_op op2; // opcode所操作的操作數
znode_op result;
ulong extended_value;
uint lineno;
zend_uchar opcode; // opcode代碼
zend_uchar op1_type;
zend_uchar op2_type;
zend_uchar result_type;
}; 

和CPU的指令類似,有一個標示指令的opcode字段,以及這個opcode所操作的操作數,PHP不像匯編那麼底層, 在腳本實際執行的時候可能還需要其他更多的信息,extended_value字段就保存了這類信息, 其中的result域則是保存該指令執行完成後的結果

例如如下代碼是在編譯器遇到print語句的時候進行編譯的函數

\php-5.6.17\Zend\zend_compile.c

void zend_do_print(znode *result, const znode *arg TSRMLS_DC) /* {{{ */
{ 
//新創建一條zend_op 
zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);

//將新建的zend_op的返回值類型設置為臨時變量(IS_TMP_VAR),因為print中的內存僅僅為了臨時輸出,並不需要保存
opline->result_type = IS_TMP_VAR;
//為臨時變量申請空間
opline->result.var = get_temporary_variable(CG(active_op_array));
//指定opcode為ZEND_PRINT
opline->opcode = ZEND_PRINT;
//將傳遞進來的參數賦值給這條opcode的第一個操作數
SET_NODE(opline->op1, arg);
SET_UNUSED(opline->op2);
GET_NODE(result, opline->result);
}

0x2: opcode類型: zend_op->zend_uchar opcode

比對匯編語言的概念,每個opcode都對應於一個類型,表明該opcpde的"操作指令",opcode的類型為zend_uchar,zend_uchar實際上就是unsigned char,此字段保存的整形值即為op的編號,用來區分不同的op類型,opcode的可取值都被定義成了宏

/Zend/zend_vm_opcodes.h

#define ZEND_NOP 0
#define ZEND_ADD 1
#define ZEND_SUB 2
#define ZEND_MUL 3
#define ZEND_DIV 4
#define ZEND_MOD 5
#define ZEND_SL 6
#define ZEND_SR 7
#define ZEND_CONCAT 8
#define ZEND_BW_OR 9
#define ZEND_BW_AND 10
#define ZEND_BW_XOR 11
#define ZEND_BW_NOT 12
#define ZEND_BOOL_NOT 13
#define ZEND_BOOL_XOR 14
#define ZEND_IS_IDENTICAL 15
#define ZEND_IS_NOT_IDENTICAL 16
#define ZEND_IS_EQUAL 17
#define ZEND_IS_NOT_EQUAL 18
#define ZEND_IS_SMALLER 19
#define ZEND_IS_SMALLER_OR_EQUAL 20
#define ZEND_CAST 21
#define ZEND_QM_ASSIGN 22
#define ZEND_ASSIGN_ADD 23
#define ZEND_ASSIGN_SUB 24
#define ZEND_ASSIGN_MUL 25
#define ZEND_ASSIGN_DIV 26
#define ZEND_ASSIGN_MOD 27
#define ZEND_ASSIGN_SL 28
#define ZEND_ASSIGN_SR 29
#define ZEND_ASSIGN_CONCAT 30
#define ZEND_ASSIGN_BW_OR 31
#define ZEND_ASSIGN_BW_AND 32
#define ZEND_ASSIGN_BW_XOR 33
#define ZEND_PRE_INC 34
#define ZEND_PRE_DEC 35
#define ZEND_POST_INC 36
#define ZEND_POST_DEC 37
#define ZEND_ASSIGN 38
#define ZEND_ASSIGN_REF 39
#define ZEND_ECHO 40
#define ZEND_PRINT 41
#define ZEND_JMP 42
#define ZEND_JMPZ 43
#define ZEND_JMPNZ 44
#define ZEND_JMPZNZ 45
#define ZEND_JMPZ_EX 46
#define ZEND_JMPNZ_EX 47
#define ZEND_CASE 48
#define ZEND_SWITCH_FREE 49
#define ZEND_BRK 50
#define ZEND_CONT 51
#define ZEND_BOOL 52
#define ZEND_INIT_STRING 53
#define ZEND_ADD_CHAR 54
#define ZEND_ADD_STRING 55
#define ZEND_ADD_VAR 56
#define ZEND_BEGIN_SILENCE 57
#define ZEND_END_SILENCE 58
#define ZEND_INIT_FCALL_BY_NAME 59
#define ZEND_DO_FCALL 60
#define ZEND_DO_FCALL_BY_NAME 61
#define ZEND_RETURN 62
#define ZEND_RECV 63
#define ZEND_RECV_INIT 64
#define ZEND_SEND_VAL 65
#define ZEND_SEND_VAR 66
#define ZEND_SEND_REF 67
#define ZEND_NEW 68
#define ZEND_INIT_NS_FCALL_BY_NAME 69
#define ZEND_FREE 70
#define ZEND_INIT_ARRAY 71
#define ZEND_ADD_ARRAY_ELEMENT 72
#define ZEND_INCLUDE_OR_EVAL 73
#define ZEND_UNSET_VAR 74
#define ZEND_UNSET_DIM 75
#define ZEND_UNSET_OBJ 76
#define ZEND_FE_RESET 77
#define ZEND_FE_FETCH 78
#define ZEND_EXIT 79
#define ZEND_FETCH_R 80
#define ZEND_FETCH_DIM_R 81
#define ZEND_FETCH_OBJ_R 82
#define ZEND_FETCH_W 83
#define ZEND_FETCH_DIM_W 84
#define ZEND_FETCH_OBJ_W 85
#define ZEND_FETCH_RW 86
#define ZEND_FETCH_DIM_RW 87
#define ZEND_FETCH_OBJ_RW 88
#define ZEND_FETCH_IS 89
#define ZEND_FETCH_DIM_IS 90
#define ZEND_FETCH_OBJ_IS 91
#define ZEND_FETCH_FUNC_ARG 92
#define ZEND_FETCH_DIM_FUNC_ARG 93
#define ZEND_FETCH_OBJ_FUNC_ARG 94
#define ZEND_FETCH_UNSET 95
#define ZEND_FETCH_DIM_UNSET 96
#define ZEND_FETCH_OBJ_UNSET 97
#define ZEND_FETCH_DIM_TMP_VAR 98
#define ZEND_FETCH_CONSTANT 99
#define ZEND_GOTO 100
#define ZEND_EXT_STMT 101
#define ZEND_EXT_FCALL_BEGIN 102
#define ZEND_EXT_FCALL_END 103
#define ZEND_EXT_NOP 104
#define ZEND_TICKS 105
#define ZEND_SEND_VAR_NO_REF 106
#define ZEND_CATCH 107
#define ZEND_THROW 108
#define ZEND_FETCH_CLASS 109
#define ZEND_CLONE 110
#define ZEND_RETURN_BY_REF 111
#define ZEND_INIT_METHOD_CALL 112
#define ZEND_INIT_STATIC_METHOD_CALL 113
#define ZEND_ISSET_ISEMPTY_VAR 114
#define ZEND_ISSET_ISEMPTY_DIM_OBJ 115
#define ZEND_PRE_INC_OBJ 132
#define ZEND_PRE_DEC_OBJ 133
#define ZEND_POST_INC_OBJ 134
#define ZEND_POST_DEC_OBJ 135
#define ZEND_ASSIGN_OBJ 136
#define ZEND_INSTANCEOF 138
#define ZEND_DECLARE_CLASS 139
#define ZEND_DECLARE_INHERITED_CLASS 140
#define ZEND_DECLARE_FUNCTION 141
#define ZEND_RAISE_ABSTRACT_ERROR 142
#define ZEND_DECLARE_CONST 143
#define ZEND_ADD_INTERFACE 144
#define ZEND_DECLARE_INHERITED_CLASS_DELAYED 145
#define ZEND_VERIFY_ABSTRACT_CLASS 146
#define ZEND_ASSIGN_DIM 147
#define ZEND_ISSET_ISEMPTY_PROP_OBJ 148
#define ZEND_HANDLE_EXCEPTION 149
#define ZEND_USER_OPCODE 150
#define ZEND_JMP_SET 152
#define ZEND_DECLARE_LAMBDA_FUNCTION 153
#define ZEND_ADD_TRAIT 154
#define ZEND_BIND_TRAITS 155
#define ZEND_SEPARATE 156
#define ZEND_QM_ASSIGN_VAR 157
#define ZEND_JMP_SET_VAR 158
#define ZEND_DISCARD_EXCEPTION 159
#define ZEND_YIELD 160
#define ZEND_GENERATOR_RETURN 161
#define ZEND_FAST_CALL 162
#define ZEND_FAST_RET 163
#define ZEND_RECV_VARIADIC 164
#define ZEND_SEND_UNPACK 165
#define ZEND_POW 166
#define ZEND_ASSIGN_POW 167 

0x3: opcode執行句柄: zend_op->handler

op的執行句柄,其類型為opcode_handler_t

typedef int (ZEND_FASTCALL *opcode_handler_t) (ZEND_OPCODE_HANDLER_ARGS);
這個函數指針為op定義了執行方式,每一種opcode字段都對應一個種類的handler,比如如果$a = 1;這樣的代碼生成的op,操作數為const和cv,最後就能確定handler為函數ZEND_ASSIGN_SPEC_CV_CONST_HANDLER

/Zend/zend_vm_execute.h

void zend_init_opcodes_handlers(void)
{
static const opcode_handler_t labels[] = {
..
ZEND_ASSIGN_SPEC_CV_CONST_HANDLER,
..
}
}

0x4: opcpde操作數znode

操作數字段是_zend_op類型中比較重要的部分了,其中op1,op2,result三個操作數定義為znode類型

\php-5.6.17\Zend\zend_compile.h

typedef struct _znode { /* used only during compilation */
/*
這個int類型的字段定義znode操作數的類型
#define IS_CONST (1<<0) //表示常量,例如$a = 123; $b = "hello";這些代碼生成OP後,123和"hello"都是以常量類型操作數存在
#define IS_TMP_VAR (1<<1) //表示臨時變量,臨時變量一般在前面加~來表示,這是一些OP執行過程中需要用到的中間變量,例如初始化一個數組的時候,就需要一個臨時變量來暫時存儲數組zval,然後將數組賦值給變量
#define IS_VAR (1<<2) //一般意義上的變量,以$開發表示
#define IS_UNUSED (1<<3) // Unused variable 
#define IS_CV (1<<4) // Compiled variable,這種類型的操作數比較重要,此類型是在PHP後來的版本中(大概5.1)中才出現,CV的意思是compiled variable,即編譯後的變量,變量都是保存在一個符號表中,這個符號表是一個哈希表,如果每次讀寫變量的時候都需要到哈希表中去檢索,會對效率有一定的影響,因此在執行上下文環境中,會將一些編譯期間生成的變量緩存起來。此類型操作數一般以!開頭表示,比如變量$a=123;$b="hello"這段代碼,$a和$b對應的操作數可能就是!0和!1, 0和1相當於一個索引號,通過索引號從緩存中取得相應的值
*/
int op_type;
/*
此字段為一個聯合體,根據op_type的不同,u取不同的值
1. op_type=IS_CONST的時候,u中的constant保存的就是操作數對應的zval結構
2. 例如$a=123時,123這個操作數中,u中的constant是一個IS_LONG類型的zval,其值lval為123 
*/
union {
znode_op op;
zval constant; /* replaced by literal/zv */
zend_op_array *op_array;
zend_ast *ast;
} u;
zend_uint EA; /* extended attributes */
} znode; 

0x5: opcode編譯後數組op_array

在zend_do_print函數中的第一行,我們注意到下面這行代碼

zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC); 

PHP腳本代碼被編譯後產生的opcode保存在op_array中,其內部存儲的結構如下

\php-5.6.17\Zend\zend_compile.h

struct _zend_op_array 
{
/* Common elements */
zend_uchar type;
const 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;
/* END of common elements */
zend_uint *refcount;
zend_op *opcodes; // opcode數組
zend_uint last;
zend_compiled_variable *vars;
int last_var;
zend_uint T;
zend_uint nested_calls;
zend_uint used_stack;
zend_brk_cont_element *brk_cont_array;
int last_brk_cont;
zend_try_catch_element *try_catch_array;
int last_try_catch;
zend_bool has_finally_block;
/* static variables support */
HashTable *static_variables;
zend_uint this_var;
const char *filename;
zend_uint line_start;
zend_uint line_end;
const char *doc_comment;
zend_uint doc_comment_len;
zend_uint early_binding; /* the linked list of delayed declarations */
zend_literal *literals;
int last_literal;
void **run_time_cache;
int last_cache_slot;
void *reserved[ZEND_MAX_RESERVED_RESOURCES];
}; 

整個PHP腳本代碼被編譯後的opcodes保存在這裡,在執行的時候由下面的execute函數執行

ZEND_API void execute(zend_op_array *op_array TSRMLS_DC)
{
// ... 循環執行op_array中的opcode或者執行其他op_array中的opcode
}

每條opcode都有一個opcode_handler_t的函數指針字段,用於執行該opcode,PHP有三種方式來進行opcode的處理

1. CALL: PHP默認使用CALL的方式,也就是函數調用的方式
2. SWITCH: 由於opcode執行是每個PHP程序頻繁需要進行的操作,可以使用SWITCH或者GOTO的方式來分發
3. GOTO: 通常GOTO的效率相對會高一些,不過效率是否提高依賴於不同的CPU
實際上我們會發現,在/zend/zend_language_parser.c中就是Zend的opcode翻譯解釋執行過程,其中包含了call、switch、goto三種opcode執行方式

這就是PHP為什麼稱之為解釋型語言的內核原理,PHP在完成Lex詞法解析後,在語法解析即生成產生式的時候,直接通過call、switch、goto的方式調用zend api進行即使解釋執行

Relevant Link:

http://www.nowamagic.net/librarys/veda/detail/1325
http://php.net/manual/zh/internals2.opcodes.list.php
http://www.nowamagic.net/librarys/veda/detail/1543
http://www.nowamagic.net/librarys/veda/detail/1324
http://www.nowamagic.net/librarys/veda/detail/1543 
http://www.laruence.com/2008/06/18/221.html
http://www.php-internals.com/book/?p=chapt02/02-03-02-opcode 

3. opcode翻譯執行(即時解釋執行)

Relevant Link:

http://www.php-internals.com/book/?p=chapt02/02-03-03-from-opcode-to-handler

以上所述本文給大家介紹的PHP內核學習教程之php opcode內核實現的相關知識,希望對大家有所幫助。

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