PHP内核学习教程之php opcode内核实现
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内核实现的相关知识,希望对大家有所帮助。