php内核分析(五)-zval
这里阅读的php版本为PHP-7.1.0 RC3,阅读代码的平台为linux 实际上,从这个函数开始,就已经进入到了zend引擎的范围了。 zend_eval_string_ex(exec_direct, NULL, "Command line code", 1) 实际上是调用Zend/zend_execute_API.c zend_eval_stringl_ex(str, strlen(str), retval_ptr, string_name, handle_exceptions); 再进去是调用 result = zend_eval_stringl(str, str_len, retval_ptr, string_name); 这里的retval_ptr为NULL,string_name为"Command line code", str为"echo 12;" zend_eval_stringl 其实这个函数主流程并不复杂。简化下来就如下 ZEND_API int zend_eval_stringl(char *str, size_t str_len, zval *retval_ptr, char *string_name) /* {{{ */ { ... new_op_array = zend_compile_string(&pv, string_name); // 这个是把php代码编译成为opcode的过程 ... zend_execute(new_op_array, &local_retval); // 这个是具体的执行过程,执行opcode,把结果存储到local_retval中 ... retval = SUCCESS; return retval; } 先把php编译为opcode,然后执行这个opcode。只是这个函数有一些关键的结构需要理一下。 zval 我们会看到 zval local_retval; 这样的变量,然后会对这个变量进行如下操作: ZVAL_UNDEF(&local_retval); ZVAL_NULL(z) ZVAL_FALSE(z) ZVAL_TRUE(z) ZVAL_BOOL(z, b) ZVAL_LONG(z, l) ZVAL_DOUBLE(z, d) ZVAL_STR(z, s) ZVAL_INTERNED_STR(z, s) ZVAL_NEW_STR(z, s) ZVAL_STR_COPY(z, s) ZVAL_ARR(z, a) ZVAL_NEW_ARR(z) ZVAL_NEW_PERSISTENT_ARR(z) ZVAL_OBJ(z, o) ZVAL_RES(z, r) ZVAL_NEW_RES(z, h, p, t) ZVAL_NEW_PERSISTENT_RES(z, h, p, t) ZVAL_REF(z, r) ZVAL_NEW_EMPTY_REF(z) ZVAL_NEW_REF(z, r) ZVAL_NEW_PERSISTENT_REF(z, r) ZVAL_NEW_AST(z, a) ZVAL_INDIRECT(z, v) ZVAL_PTR(z, p) ZVAL_FUNC(z, f) ZVAL_CE(z, c) ZVAL_ERROR(z) php是一个弱类型的语言,它可以用一个$var来代表string,int,array,object等。这个就是归功于zval_struct结构 // zval的结构 struct _zval_struct { zend_value value; // 存储具体值,它的结构根据类型不同而不同 union { struct { ZEND_ENDIAN_LOHI_4( zend_uchar type, // 这个位置标记了这个val是什么类型的(IS_STRING/IS_INT) zend_uchar type_flags, // 这个位置标记了这个val是什么属性 (IS_CALLABLE等) zend_uchar const_flags, // 常量的一些属性 (IS_CONSTANT_CLASS) zend_uchar reserved) // 保留的一些字段 } v; uint32_t type_info; // 类型的一些额外信息 } u1; // 保存类型的一些关键信息 union { uint32_t next; // 如果是在hash链表中,这个指针代表下一个元素的index uint32_t cache_slot; /* literal cache slot */ uint32_t lineno; /* line number (for ast nodes) */ uint32_t num_args; /* arguments number for EX(This) */ uint32_t fe_pos; /* foreach position */ uint32_t fe_iter_idx; /* foreach iterator index */ uint32_t access_flags; /* class constant access flags */ uint32_t property_guard; /* single property guard */ } u2; // 一些附属字段 }; 这个接口最重要的两个字段是 value,存储变量的值。另一个是u1.v.type 存储变量的类型。这里,value也是一个结构 typedef union _zend_value { zend_long lval; /* long value */ double dval; /* double value */ zend_refcounted *counted; zend_string *str; // string zend_array *arr; // array zend_object *obj; // object zend_resource *res; // resource zend_reference *ref; // 指针 zend_ast_ref *ast; // ast指针 zval *zv; void *ptr; zend_class_entry *ce; // class实体 zend_function *func; // 函数实体 struct { uint32_t w1; uint32_t w2; } ww; } zend_value; 如果u1.v.type == IS_STRING, 那么value.str就是指向了zend_string结构。好了,php的垃圾回收是通过引用计数来进行的,这个引用计数的计数器就放在zval.value.counted里面。 我们对zval设置的时候设置了一些宏来进行设置,比如:ZVAL_STRINGL是设置string,我们仔细看下调用堆栈: ZVAL_STRINGL(&pv, str, str_len); // 把pv设置为string类型,值为str 这个函数就是把pv设置为zend_string类型 // 带字符串长度的设置zend_sting类型的zval #define ZVAL_STRINGL(z, s, l) do { \ ZVAL_NEW_STR(z, zend_string_init(s, l, 0)); \ } while (0) 注意到,这里使用了一个写法,do {} while(0) 来设置一个宏,这个是C里面比较好的写法,这样写,能保证宏中定义的东西在for,if,等各种流程语句中不会出现语法错误。不过其实我们学习代码的时候,可以忽略掉这个框框写法。 zend_string_init(s, l, 0) ... // 从char* + 长度 + 是否是临时变量(persistent为0表示最迟这个申请的空间在请求结束的时候就进行释放),转变为zend_string* static zend_always_inline zend_string *zend_string_init(const char *str, size_t len, int persistent) { zend_string *ret = zend_string_alloc(len, persistent); // 申请空间,申请的大小为zend_string结构大小(除了val)+ len + 1 memcpy(ZSTR_VAL(ret), str, len); ZSTR_VAL(ret)[len] = '\0'; return ret; } 这个函数可以看的点有几个: persistent 这个参数是用来代表申请的空间是不是“临时”的。这里说的临时是zend提供的一种内存管理器,相关请求数据只服务于单个请求,最迟会在请求结束的时候释放。 临时内存申请对应的函数为: void *emalloc(size_t size) 而永久内存申请对应的函数为: malloc zend_string_alloc static zend_always_inline zend_string *zend_string_alloc(size_t len, int persistent) { zend_string *ret = (zend_string *)pemalloc(ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(len)), persistent); GC_REFCOUNT(ret) = 1; GC_TYPE_INFO(ret) = IS_STRING | ((persistent ? IS_STR_PERSISTENT : 0) << 8); zend_string_forget_hash_val(ret); ZSTR_LEN(ret) = len; return ret; } 我们先看看zend_string的结构: // 字符串 struct _zend_string { zend_refcounted_h gc; // gc使用的被引用的次数 zend_ulong h; // 如果这个字符串作为hashtable的key在查找时候需要重复计算它的hash值,所以保存一份在这里 size_t len; // 字符串长度 char val[1]; // 柔性数组,虽然我们定义了数组只有一个元素,但是在实际分配内存的时候,会分配足够的内存 }; _ZSTR_STRUCT_SIZE(len) gc+h+len的空间,最后给了val留了len+1的长度 #define _ZSTR_STRUCT_SIZE(len) (_ZSTR_HEADER_SIZE + len + 1) ## GC_REFCOUNT(ret) = 1; #define GC_REFCOUNT(p) (p)->gc.refcount 这里就看到一个结构zend_refcounted_h typedef struct _zend_refcounted_h { uint32_t refcount; // 真正的计数 union { struct { ZEND_ENDIAN_LOHI_3( zend_uchar type, // 冗余了zval中的类型值 zend_uchar flags, // used for strings & objects中有特定作用 uint16_t gc_info) // 在GC缓冲区中的索引位置 } v; uint32_t type_info; // 冗余zval中的type_info } u; // 类型信息 } zend_refcounted_h; 回到我们的实例,我们调用的是 zend_string_init(s, l, 0) // s=char*(echo 12;) l=8 返回的zend_string实际值为: struct _zend_string { struct { uint32_t refcount; // 1 union { struct { ZEND_ENDIAN_LOHI_3( zend_uchar type, // IS_STRING zend_uchar flags, uint16_t gc_info) } v; uint32_t type_info; //IS_STRING | 0 => IS_STRING } u; } gc; zend_ulong h; // 0 size_t len; // 8 char val[1]; // echo 12;\0 }; 结合到zval里面,那么ZVAL_STRINGL(&pv, str, str_len);返回的zval为 // zval的结构 struct _zval_struct { union _zend_value { zend_long lval; double dval; zend_refcounted *counted; zend_string *str; // 指向到上面定义的那个zend_string中 zend_array *arr; zend_object *obj; zend_resource *res; zend_reference *ref; zend_ast_ref *ast; zval *zv; void *ptr; zend_class_entry *ce; zend_function *func; struct { uint32_t w1; uint32_t w2; } ww; } value; union { struct { ZEND_ENDIAN_LOHI_4( zend_uchar type, zend_uchar type_flags, zend_uchar const_flags, zend_uchar reserved) } v; uint32_t type_info; // IS_STRING_EX } u1; union { uint32_t next; uint32_t cache_slot; uint32_t lineno; uint32_t num_args; uint32_t fe_pos; uint32_t fe_iter_idx; uint32_t access_flags; uint32_t property_guard; } u2; }; 这里,就对zval结构有初步了解了。 另外建议记住几个常用的类型,后续调试的时候会很有用 /* regular data types */ #define IS_UNDEF 0 #define IS_NULL 1 #define IS_FALSE 2 #define IS_TRUE 3 #define IS_LONG 4 #define IS_DOUBLE 5 #define IS_STRING 6 #define IS_ARRAY 7 #define IS_OBJECT 8 #define IS_RESOURCE 9 #define IS_REFERENCE 10 /* constant expressions */ #define IS_CONSTANT 11 #define IS_CONSTANT_AST 12 本文转自轩脉刃博客园博客,原文链接:http://www.cnblogs.com/yjf512/p/6108444.html,如需转载请自行联系原作者