Maison  >  Article  >  php教程  >  php内核分析(五)-zval

php内核分析(五)-zval

黄舟
黄舟original
2016-12-19 11:19:051743parcourir

摘要:这里阅读的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中文网(www.php.cn)!


Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn