当前位置:首页 > PHP教程 > php高级应用 > 列表

PHP内核学习教程之php opcode内核实现

发布:smiling 来源: PHP粉丝网  添加日期:2021-07-07 13:48:16 浏览: 评论:0 

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

  1. struct _zend_op  
  2. opcode_handler_t handler; // 执行该opcode时调用的处理函数 
  3. znode_op op1; // opcode所操作的操作数 
  4. znode_op op2; // opcode所操作的操作数 
  5. znode_op result; 
  6. ulong extended_value; 
  7. uint lineno; 
  8. zend_uchar opcode; // opcode代码 
  9. zend_uchar op1_type; 
  10. zend_uchar op2_type; 
  11. zend_uchar result_type; 
  12. }; 

和CPU的指令类似,有一个标示指令的opcode字段,以及这个opcode所操作的操作数,PHP不像汇编那么底层, 在脚本实际执行的时候可能还需要其他更多的信息,extended_value字段就保存了这类信息, 其中的result域则是保存该指令执行完成后的结果

例如如下代码是在编译器遇到print语句的时候进行编译的函数

\php-5.6.17\Zend\zend_compile.c

  1. void zend_do_print(znode *result, const znode *arg TSRMLS_DC) /* {{{ */ 
  2. {  
  3. //新创建一条zend_op  
  4. zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC); 
  5.  
  6. //将新建的zend_op的返回值类型设置为临时变量(IS_TMP_VAR),因为print中的内存仅仅为了临时输出,并不需要保存 
  7. opline->result_type = IS_TMP_VAR; 
  8. //为临时变量申请空间 
  9. opline->result.var = get_temporary_variable(CG(active_op_array)); 
  10. //指定opcode为ZEND_PRINT 
  11. opline->opcode = ZEND_PRINT; 
  12. //将传递进来的参数赋值给这条opcode的第一个操作数 
  13. SET_NODE(opline->op1, arg); 
  14. SET_UNUSED(opline->op2); 
  15. 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

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

  1. void zend_init_opcodes_handlers(void) 
  2. static const opcode_handler_t labels[] = { 
  3. .. 
  4. ZEND_ASSIGN_SPEC_CV_CONST_HANDLER, 
  5. .. 

0x4: opcpde操作数znode

操作数字段是_zend_op类型中比较重要的部分了,其中op1,op2,result三个操作数定义为znode类型

\php-5.6.17\Zend\zend_compile.h

  1. typedef struct _znode { /* used only during compilation */ 
  2. /* 
  3. 这个int类型的字段定义znode操作数的类型 
  4. #define IS_CONST (1<<0) //表示常量,例如$a = 123; $b = "hello";这些代码生成OP后,123和"hello"都是以常量类型操作数存在 
  5. #define IS_TMP_VAR (1<<1) //表示临时变量,临时变量一般在前面加~来表示,这是一些OP执行过程中需要用到的中间变量,例如初始化一个数组的时候,就需要一个临时变量来暂时存储数组zval,然后将数组赋值给变量 
  6. #define IS_VAR (1<<2) //一般意义上的变量,以$开发表示 
  7. #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

  1. */ 
  2. union { 
  3. znode_op op; 
  4. zval constant; /* replaced by literal/zv */ 
  5. zend_op_array *op_array; 
  6. zend_ast *ast; 
  7. } u; 
  8. zend_uint EA; /* extended attributes */ 
  9. } 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

  1. struct _zend_op_array  
  2. /* Common elements */ 
  3. zend_uchar type; 
  4. const char *function_name; // 如果是用户定义的函数则,这里将保存函数的名字 
  5. zend_class_entry *scope; 
  6. zend_uint fn_flags; 
  7. union _zend_function *prototype; 
  8. zend_uint num_args; 
  9. zend_uint required_num_args; 
  10. zend_arg_info *arg_info; 
  11. /* END of common elements */ 
  12. zend_uint *refcount; 
  13. zend_op *opcodes; // opcode数组 
  14. zend_uint last; 
  15. zend_compiled_variable *vars; 
  16. int last_var; 
  17. zend_uint T; 
  18. zend_uint nested_calls; 
  19. zend_uint used_stack; 
  20. zend_brk_cont_element *brk_cont_array; 
  21. int last_brk_cont; 
  22. zend_try_catch_element *try_catch_array; 
  23. int last_try_catch; 
  24. zend_bool has_finally_block; 
  25. /* static variables support */ 
  26. HashTable *static_variables; 
  27. zend_uint this_var; 
  28. const char *filename; 
  29. zend_uint line_start; 
  30. zend_uint line_end; 
  31. const char *doc_comment; 
  32. zend_uint doc_comment_len; 
  33. zend_uint early_binding; /* the linked list of delayed declarations */ 
  34. zend_literal *literals; 
  35. int last_literal; 
  36. void **run_time_cache; 
  37. int last_cache_slot; 
  38. void *reserved[ZEND_MAX_RESERVED_RESOURCES]; 
  39. }; 

整个PHP脚本代码被编译后的opcodes保存在这里,在执行的时候由下面的execute函数执行:

  1. ZEND_API void execute(zend_op_array *op_array TSRMLS_DC) 
  2. // ... 循环执行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:

  1. http://www.nowamagic.net/librarys/veda/detail/1325 
  2. http://php.net/manual/zh/internals2.opcodes.list.php 
  3. http://www.nowamagic.net/librarys/veda/detail/1543 
  4. http://www.nowamagic.net/librarys/veda/detail/1324 
  5. http://www.nowamagic.net/librarys/veda/detail/1543  
  6. http://www.laruence.com/2008/06/18/221.html 
  7. 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

Tags: PHP内核学习 opcode

分享到: