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/lizhenghn/p/3674430.html
http://0x1.im/blog/php/Internal-value-representation-in-PHP-7-part-1.html
http://0x1.im/blog/php/Internal-value-representation-in-PHP-7-part-2.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM