zoukankan      html  css  js  c++  java
  • php内核解析(二)

    里阅读的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] = '';
        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;
    };

    结合到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/
  • 相关阅读:
    Java内部类总结
    利用mysqld_multi启动管理多实例
    L001-老男孩教育-Python13期VIP视频-19节-pbb
    L014-oldboy-mysql-dba-lesson14
    L012-oldboy-mysql-dba-lesson12
    L013-oldboy-mysql-dba-lesson13
    L010-oldboy-mysql-dba-lesson10
    L011-oldboy-mysql-dba-lesson11
    L009-oldboy-mysql-dba-lesson09
    L007-oldboy-mysql-dba-lesson07
  • 原文地址:https://www.cnblogs.com/guoyong-feng/p/6115733.html
Copyright © 2011-2022 走看看