PHPカーネル解析(5)-zval

黄舟
リリース: 2016-12-19 11:19:05
オリジナル
1659 人が閲覧しました

摘要:这里阅读的php版本为PHP-7.1.0 RC3,阅读代码的平台为linux实际上,从这个函数开始,就已经进入到了zend引擎的范围了。zend_eval_string_ex(exec_direct, NULL, "Command line code", 1) 实际上是调用Zend/zend_exec ...

这里阅读的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


其实这个函数主流程并不复杂。简化下来就如下

01 ZEND_API int zend_eval_stringl(char *str, size_t str_len, zval *retval_ptr, char *string_name) /* {{{ */ 02 { 03 ... 04 new_op_array = zend_compile_string(&pv, string_name); // 这个是把php代码编译成为opcode的过程 05 ... 06 zend_execute(new_op_array, &local_retval); // 这个是具体的执行过程,执行opcode,把结果存储到local_retval中 07 ... 08 retval = SUCCESS; 09 return retval; 10 }
ログイン後にコピー

先把php编译为opcode,然后执行这个opcode。只是这个函数有一些关键的结构需要理一下。

zval


我们会看到

zval local_retval;
ログイン後にコピー

这样的变量,然后会对这个变量进行如下操作:

01 ZVAL_UNDEF(&local_retval); 02 03 ZVAL_NULL(z) 04 ZVAL_FALSE(z) 05 ZVAL_TRUE(z) 06 ZVAL_BOOL(z, b) 07 ZVAL_LONG(z, l) 08 ZVAL_DOUBLE(z, d) 09 ZVAL_STR(z, s) 10 ZVAL_INTERNED_STR(z, s) 11 ZVAL_NEW_STR(z, s) 12 ZVAL_STR_COPY(z, s) 13 ZVAL_ARR(z, a) 14 ZVAL_NEW_ARR(z) 15 ZVAL_NEW_PERSISTENT_ARR(z) 16 ZVAL_OBJ(z, o) 17 ZVAL_RES(z, r) 18 ZVAL_NEW_RES(z, h, p, t) 19 ZVAL_NEW_PERSISTENT_RES(z, h, p, t) 20 ZVAL_REF(z, r) 21 ZVAL_NEW_EMPTY_REF(z) 22 ZVAL_NEW_REF(z, r) 23 ZVAL_NEW_PERSISTENT_REF(z, r) 24 ZVAL_NEW_AST(z, a) 25 ZVAL_INDIRECT(z, v) 26 ZVAL_PTR(z, p) 27 ZVAL_FUNC(z, f) 28 ZVAL_CE(z, c) 29 ZVAL_ERROR(z)
ログイン後にコピー

php是一个弱类型的语言,它可以用一个$var来代表string,int,array,object等。这个就是归功于zval_struct结构

01 // zval的结构 02 struct _zval_struct { 03 zend_value value; // 存储具体值,它的结构根据类型不同而不同 04 union { 05 struct { 06 ZEND_ENDIAN_LOHI_4( 07 zend_uchar type, // 这个位置标记了这个val是什么类型的(IS_STRING/IS_INT) 08 zend_uchar type_flags, // 这个位置标记了这个val是什么属性 (IS_CALLABLE等) 09 zend_uchar const_flags, // 常量的一些属性 (IS_CONSTANT_CLASS) 10 zend_uchar reserved) // 保留的一些字段 11 } v; 12 uint32_t type_info; // 类型的一些额外信息 13 } u1; // 保存类型的一些关键信息 14 union { 15 uint32_t next; // 如果是在hash链表中,这个指针代表下一个元素的index 16 uint32_t cache_slot; /* literal cache slot */ 17 uint32_t lineno; /* line number (for ast nodes) */ 18 uint32_t num_args; /* arguments number for EX(This) */ 19 uint32_t fe_pos; /* foreach position */ 20 uint32_t fe_iter_idx; /* foreach iterator index */ 21 uint32_t access_flags; /* class constant access flags */ 22 uint32_t property_guard; /* single property guard */ 23 } u2; // 一些附属字段 24 };
ログイン後にコピー

这个接口最重要的两个字段是 value,存储变量的值。另一个是u1.v.type 存储变量的类型。这里,value也是一个结构

01 typedef union _zend_value { 02 zend_long lval; /* long value */ 03 double dval; /* double value */ 04 zend_refcounted *counted; 05 zend_string *str; // string 06 zend_array *arr; // array 07 zend_object *obj; // object 08 zend_resource *res; // resource 09 zend_reference *ref; // 指针 10 zend_ast_ref *ast; // ast指针 11 zval *zv; 12 void *ptr; 13 zend_class_entry *ce; // class实体 14 zend_function *func; // 函数实体 15 struct { 16 uint32_t w1; 17 uint32_t w2; 18 } ww; 19 } 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类型

1 // 带字符串长度的设置zend_sting类型的zval 2 #define ZVAL_STRINGL(z, s, l) do { \ 3 ZVAL_NEW_STR(z, zend_string_init(s, l, 0)); \ 4 } while (0)
ログイン後にコピー

注意到,这里使用了一个写法,do {} while(0) 来设置一个宏,这个是C里面比较好的写法,这样写,能保证宏中定义的东西在for,if,等各种流程语句中不会出现语法错误。不过其实我们学习代码的时候,可以忽略掉这个框框写法。

01 zend_string_init(s, l, 0) 02 ... 03 04 // 从char* + 长度 + 是否是临时变量(persistent为0表示最迟这个申请的空间在请求结束的时候就进行释放),转变为zend_string* 05 static zend_always_inline zend_string *zend_string_init(const char *str, size_t len, int persistent) 06 { 07 zend_string *ret = zend_string_alloc(len, persistent); // 申请空间,申请的大小为zend_string结构大小(除了val)+ len + 1 08 09 memcpy(ZSTR_VAL(ret), str, len); 10 ZSTR_VAL(ret)[len] = '\0'; 11 return ret; 12 }
ログイン後にコピー

这个函数可以看的点有几个:

persistent

这个参数是用来代表申请的空间是不是“临时”的。这里说的临时是zend提供的一种内存管理器,相关请求数据只服务于单个请求,最迟会在请求结束的时候释放。

临时内存申请对应的函数为:

void *emalloc(size_t size)
ログイン後にコピー

而永久内存申请对应的函数为:

malloc
ログイン後にコピー

zend_string_alloc

01 static zend_always_inline zend_string *zend_string_alloc(size_t len, int persistent) 02 { 03 zend_string *ret = (zend_string *)pemalloc(ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(len)), persistent); 04 05 GC_REFCOUNT(ret) = 1; 06 07 GC_TYPE_INFO(ret) = IS_STRING | ((persistent ? IS_STR_PERSISTENT : 0) << 8); 08 09 zend_string_forget_hash_val(ret); 10 ZSTR_LEN(ret) = len; 11 return ret; 12 }
ログイン後にコピー

我们先看看zend_string的结构:

01 // 字符串 02 struct _zend_string { 03 zend_refcounted_h gc; // gc使用的被引用的次数 04 zend_ulong h; // 如果这个字符串作为hashtable的key在查找时候需要重复计算它的hash值,所以保存一份在这里 05 size_t len; // 字符串长度 06 char val[1]; // 柔性数组,虽然我们定义了数组只有一个元素,但是在实际分配内存的时候,会分配足够的内存 07 }; 08 09 10 _ZSTR_STRUCT_SIZE(len) gc+h+len的空间,最后给了val留了len+1的长度 11 12 #define _ZSTR_STRUCT_SIZE(len) (_ZSTR_HEADER_SIZE + len + 1) 13 14 ## GC_REFCOUNT(ret) = 1; 15 16 #define GC_REFCOUNT(p) (p)->gc.refcount
ログイン後にコピー

这里就看到一个结构zend_refcounted_h

01 typedef struct _zend_refcounted_h { 02 uint32_t refcount; // 真正的计数 03 union { 04 struct { 05 ZEND_ENDIAN_LOHI_3( 06 zend_uchar type, // 冗余了zval中的类型值 07 zend_uchar flags, // used for strings & objects中有特定作用 08 uint16_t gc_info) // 在GC缓冲区中的索引位置 09 } v; 10 uint32_t type_info; // 冗余zval中的type_info 11 } u; // 类型信息 12 } zend_refcounted_h;
ログイン後にコピー

回到我们的实例,我们调用的是

zend_string_init(s, l, 0) // s=char*(echo 12;) l=8
ログイン後にコピー

返回的zend_string实际值为:

01 struct _zend_string { 02 struct { 03 uint32_t refcount; // 1 04 union { 05 struct { 06 ZEND_ENDIAN_LOHI_3( 07 zend_uchar type, // IS_STRING 08 zend_uchar flags, 09 uint16_t gc_info) 10 } v; 11 uint32_t type_info; //IS_STRING | 0 => IS_STRING 12 } u; 13 } gc; 14 zend_ulong h; // 0 15 size_t len; // 8 16 char val[1]; // echo 12;\0 17 };
ログイン後にコピー

结合到zval里面,那么ZVAL_STRINGL(&pv, str, str_len);返回的zval为

01 // zval的结构 02 struct _zval_struct { 03 union _zend_value { 04 zend_long lval; 05 double dval; 06 zend_refcounted *counted; 07 zend_string *str; // 指向到上面定义的那个zend_string中 08 zend_array *arr; 09 zend_object *obj; 10 zend_resource *res; 11 zend_reference *ref; 12 zend_ast_ref *ast; 13 zval *zv; 14 void *ptr; 15 zend_class_entry *ce; 16 zend_function *func; 17 struct { 18 uint32_t w1; 19 uint32_t w2; 20 } ww; 21 } value; 22 union { 23 struct { 24 ZEND_ENDIAN_LOHI_4( 25 zend_uchar type, 26 zend_uchar type_flags, 27 zend_uchar const_flags, 28 zend_uchar reserved) 29 } v; 30 uint32_t type_info; // IS_STRING_EX 31 } u1; 32 union { 33 uint32_t next; 34 uint32_t cache_slot; 35 uint32_t lineno; 36 uint32_t num_args; 37 uint32_t fe_pos; 38 uint32_t fe_iter_idx; 39 uint32_t access_flags; 40 uint32_t property_guard; 41 } u2; 42 };
ログイン後にコピー

这里,就对zval结构有初步了解了。

另外建议记住几个常用的类型,后续调试的时候会很有用

01 /* regular data types */ 02 #define IS_UNDEF 0 03 #define IS_NULL 1 04 #define IS_FALSE 2 05 #define IS_TRUE 3 06 #define IS_LONG 4 07 #define IS_DOUBLE 5 08 #define IS_STRING 6 09 #define IS_ARRAY 7 10 #define IS_OBJECT 8 11 #define IS_RESOURCE 9 12 #define IS_REFERENCE 10 13 14 /* constant expressions */ 15 #define IS_CONSTANT 11 16 #define IS_CONSTANT_AST 12
ログイン後にコピー

以上就是php内核分析(五)-zval的内容,更多相关内容请关注PHP中文网(m.sbmmt.com)!


関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のおすすめ
    人気のチュートリアル
    詳細>
    最新のダウンロード
    詳細>
    ウェブエフェクト
    公式サイト
    サイト素材
    フロントエンドテンプレート
    私たちについて 免責事項 Sitemap
    PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!