PHP內(nèi)核學(xué)習(xí)教程之php opcode內(nèi)核實(shí)現(xiàn)
opcode是計(jì)算機(jī)指令中的一部分,用于指定要執(zhí)行的操作, 指令的格式和規(guī)范由處理器的指令規(guī)范指定。 除了指令本身以外通常還有指令所需要的操作數(shù),可能有的指令不需要顯式的操作數(shù)。 這些操作數(shù)可能是寄存器中的值,堆棧中的值,某塊內(nèi)存的值或者IO端口中的值等等。
通常opcode還有另一種稱謂:字節(jié)碼(byte codes)。 例如Java虛擬機(jī)(JVM),.NET的通用中間語(yǔ)言(CIL: Common Intermeditate Language)等等。
1. Opcode簡(jiǎn)介
opcode是計(jì)算機(jī)指令中的一部分,用于指定要執(zhí)行的操作, 指令的格式和規(guī)范由處理器的指令規(guī)范指定。 除了指令本身以外通常還有指令所需要的操作數(shù),可能有的指令不需要顯式的操作數(shù)。 這些操作數(shù)可能是寄存器中的值,堆棧中的值,某塊內(nèi)存的值或者IO端口中的值等等
通常opcode還有另一種稱謂: 字節(jié)碼(byte codes)。 例如Java虛擬機(jī)(JVM),.NET的通用中間語(yǔ)言(CIL: Common Intermeditate Language)等等
PHP中的opcode則屬于前面介紹中的后著,PHP是構(gòu)建在Zend虛擬機(jī)(Zend VM)之上的。PHP的opcode就是Zend虛擬機(jī)中的指令(基于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: 數(shù)據(jù)結(jié)構(gòu)
在PHP實(shí)現(xiàn)內(nèi)部,opcode由如下的結(jié)構(gòu)體表示
\php-5.6.17\Zend\zend_compile.h
struct _zend_op { opcode_handler_t handler; // 執(zhí)行該opcode時(shí)調(diào)用的處理函數(shù) znode_op op1; // opcode所操作的操作數(shù) znode_op op2; // opcode所操作的操作數(shù) 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的指令類(lèi)似,有一個(gè)標(biāo)示指令的opcode字段,以及這個(gè)opcode所操作的操作數(shù),PHP不像匯編那么底層, 在腳本實(shí)際執(zhí)行的時(shí)候可能還需要其他更多的信息,extended_value字段就保存了這類(lèi)信息, 其中的result域則是保存該指令執(zhí)行完成后的結(jié)果
例如如下代碼是在編譯器遇到print語(yǔ)句的時(shí)候進(jìn)行編譯的函數(shù)
\php-5.6.17\Zend\zend_compile.c
void zend_do_print(znode *result, const znode *arg TSRMLS_DC) /* {{{ */ { //新創(chuàng)建一條zend_op zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC); //將新建的zend_op的返回值類(lèi)型設(shè)置為臨時(shí)變量(IS_TMP_VAR),因?yàn)閜rint中的內(nèi)存僅僅為了臨時(shí)輸出,并不需要保存 opline->result_type = IS_TMP_VAR; //為臨時(shí)變量申請(qǐng)空間 opline->result.var = get_temporary_variable(CG(active_op_array)); //指定opcode為ZEND_PRINT opline->opcode = ZEND_PRINT; //將傳遞進(jìn)來(lái)的參數(shù)賦值給這條opcode的第一個(gè)操作數(shù) SET_NODE(opline->op1, arg); SET_UNUSED(opline->op2); GET_NODE(result, opline->result); }
0x2: opcode類(lèi)型: zend_op->zend_uchar opcode
比對(duì)匯編語(yǔ)言的概念,每個(gè)opcode都對(duì)應(yīng)于一個(gè)類(lèi)型,表明該opcpde的"操作指令",opcode的類(lèi)型為zend_uchar,zend_uchar實(shí)際上就是unsigned char,此字段保存的整形值即為op的編號(hào),用來(lái)區(qū)分不同的op類(lèi)型,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執(zhí)行句柄: zend_op->handler
op的執(zhí)行句柄,其類(lèi)型為opcode_handler_t
typedef int (ZEND_FASTCALL *opcode_handler_t) (ZEND_OPCODE_HANDLER_ARGS);
這個(gè)函數(shù)指針為op定義了執(zhí)行方式,每一種opcode字段都對(duì)應(yīng)一個(gè)種類(lèi)的handler,比如如果$a = 1;這樣的代碼生成的op,操作數(shù)為const和cv,最后就能確定handler為函數(shù)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操作數(shù)znode
操作數(shù)字段是_zend_op類(lèi)型中比較重要的部分了,其中op1,op2,result三個(gè)操作數(shù)定義為znode類(lèi)型
\php-5.6.17\Zend\zend_compile.h
typedef struct _znode { /* used only during compilation */ /* 這個(gè)int類(lèi)型的字段定義znode操作數(shù)的類(lèi)型 #define IS_CONST (1<<0) //表示常量,例如$a = 123; $b = "hello";這些代碼生成OP后,123和"hello"都是以常量類(lèi)型操作數(shù)存在 #define IS_TMP_VAR (1<<1) //表示臨時(shí)變量,臨時(shí)變量一般在前面加~來(lái)表示,這是一些OP執(zhí)行過(guò)程中需要用到的中間變量,例如初始化一個(gè)數(shù)組的時(shí)候,就需要一個(gè)臨時(shí)變量來(lái)暫時(shí)存儲(chǔ)數(shù)組zval,然后將數(shù)組賦值給變量 #define IS_VAR (1<<2) //一般意義上的變量,以$開(kāi)發(fā)表示 #define IS_UNUSED (1<<3) // Unused variable #define IS_CV (1<<4) // Compiled variable,這種類(lèi)型的操作數(shù)比較重要,此類(lèi)型是在PHP后來(lái)的版本中(大概5.1)中才出現(xiàn),CV的意思是compiled variable,即編譯后的變量,變量都是保存在一個(gè)符號(hào)表中,這個(gè)符號(hào)表是一個(gè)哈希表,如果每次讀寫(xiě)變量的時(shí)候都需要到哈希表中去檢索,會(huì)對(duì)效率有一定的影響,因此在執(zhí)行上下文環(huán)境中,會(huì)將一些編譯期間生成的變量緩存起來(lái)。此類(lèi)型操作數(shù)一般以!開(kāi)頭表示,比如變量$a=123;$b="hello"這段代碼,$a和$b對(duì)應(yīng)的操作數(shù)可能就是!0和!1, 0和1相當(dāng)于一個(gè)索引號(hào),通過(guò)索引號(hào)從緩存中取得相應(yīng)的值 */ int op_type; /* 此字段為一個(gè)聯(lián)合體,根據(jù)op_type的不同,u取不同的值 1. op_type=IS_CONST的時(shí)候,u中的constant保存的就是操作數(shù)對(duì)應(yīng)的zval結(jié)構(gòu) 2. 例如$a=123時(shí),123這個(gè)操作數(shù)中,u中的constant是一個(gè)IS_LONG類(lèi)型的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編譯后數(shù)組op_array
在zend_do_print函數(shù)中的第一行,我們注意到下面這行代碼
zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);
PHP腳本代碼被編譯后產(chǎn)生的opcode保存在op_array中,其內(nèi)部存儲(chǔ)的結(jié)構(gòu)如下
\php-5.6.17\Zend\zend_compile.h
struct _zend_op_array { /* Common elements */ zend_uchar type; const char *function_name; // 如果是用戶定義的函數(shù)則,這里將保存函數(shù)的名字 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數(shù)組 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]; };
整個(gè)PHP腳本代碼被編譯后的opcodes保存在這里,在執(zhí)行的時(shí)候由下面的execute函數(shù)執(zhí)行
ZEND_API void execute(zend_op_array *op_array TSRMLS_DC) { // ... 循環(huán)執(zhí)行op_array中的opcode或者執(zhí)行其他op_array中的opcode }
每條opcode都有一個(gè)opcode_handler_t的函數(shù)指針字段,用于執(zhí)行該opcode,PHP有三種方式來(lái)進(jìn)行opcode的處理
1. CALL: PHP默認(rèn)使用CALL的方式,也就是函數(shù)調(diào)用的方式
2. SWITCH: 由于opcode執(zhí)行是每個(gè)PHP程序頻繁需要進(jìn)行的操作,可以使用SWITCH或者GOTO的方式來(lái)分發(fā)
3. GOTO: 通常GOTO的效率相對(duì)會(huì)高一些,不過(guò)效率是否提高依賴于不同的CPU
實(shí)際上我們會(huì)發(fā)現(xiàn),在/zend/zend_language_parser.c中就是Zend的opcode翻譯解釋執(zhí)行過(guò)程,其中包含了call、switch、goto三種opcode執(zhí)行方式
這就是PHP為什么稱之為解釋型語(yǔ)言的內(nèi)核原理,PHP在完成Lex詞法解析后,在語(yǔ)法解析即生成產(chǎn)生式的時(shí)候,直接通過(guò)call、switch、goto的方式調(diào)用zend api進(jìn)行即使解釋執(zhí)行
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翻譯執(zhí)行(即時(shí)解釋執(zhí)行)
Relevant Link:
http://www.php-internals.com/book/?p=chapt02/02-03-03-from-opcode-to-handler
以上所述本文給大家介紹的PHP內(nèi)核學(xué)習(xí)教程之php opcode內(nèi)核實(shí)現(xiàn)的相關(guān)知識(shí),希望對(duì)大家有所幫助。
相關(guān)文章
tp5框架基于Ajax實(shí)現(xiàn)列表無(wú)刷新排序功能示例
這篇文章主要介紹了tp5框架基于Ajax實(shí)現(xiàn)列表無(wú)刷新排序功能,結(jié)合實(shí)例形式詳細(xì)分析了thinkPHP5結(jié)合Ajax實(shí)現(xiàn)列表無(wú)刷新排序的原理、操作步驟與相關(guān)注意事項(xiàng),需要的朋友可以參考下2020-02-02php微信公眾號(hào)開(kāi)發(fā)之翻頁(yè)查詢
這篇文章主要為大家詳細(xì)介紹了php微信公眾號(hào)開(kāi)發(fā)之翻頁(yè)查詢功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10thinkPHP5.1框架路由::get、post請(qǐng)求簡(jiǎn)單用法示例
這篇文章主要介紹了thinkPHP5.1框架路由::get、post請(qǐng)求簡(jiǎn)單用法,結(jié)合實(shí)例形式分析了thinkPHP5.1路由get、post請(qǐng)求基本使用方法,需要的朋友可以參考下2019-05-05thinkPHP框架中l(wèi)ayer.js的封裝與使用方法示例
這篇文章主要介紹了thinkPHP框架中l(wèi)ayer.js的封裝與使用方法,結(jié)合實(shí)例形式分析了thinkPHP中調(diào)用layer.js的具體操作技巧與注意事項(xiàng),需要的朋友可以參考下2019-01-01使用composer安裝使用thinkphp6.0框架問(wèn)題【視頻教程】
這篇文章主要介紹了使用composer安裝使用thinkphp6.0框架問(wèn)題,通過(guò)一個(gè)小視頻給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-10-10