- 相關(guān)推薦
php內(nèi)核分析之zval
學(xué)習(xí)PHP的同學(xué)對(duì)php內(nèi)核方面的知識(shí)也許了解的還不是很清楚,那么下面小編就php內(nèi)核之zval展開(kāi)分析,希望對(duì)大家有用,更多內(nèi)容請(qǐng)關(guān)注應(yīng)屆畢業(yè)生網(wǎng)!
這里閱讀的php版本為PHP-7.1.0 RC3,閱讀代碼的平臺(tái)為linux
實(shí)際上,從這個(gè)函數(shù)開(kāi)始,就已經(jīng)進(jìn)入到了zend引擎的范圍了。
zend_eval_string_ex(exec_direct, NULL, "Command line code", 1)
實(shí)際上是調(diào)用Zend/zend_execute_API.c
zend_eval_stringl_ex(str, strlen(str), retval_ptr, string_name, handle_exceptions);
再進(jìn)去是調(diào)用
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
其實(shí)這個(gè)函數(shù)主流程并不復(fù)雜。簡(jiǎn)化下來(lái)就如下
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); // 這個(gè)是把php代碼編譯成為opcode的過(guò)程
...
zend_execute(new_op_array, &local_retval); // 這個(gè)是具體的.執(zhí)行過(guò)程,執(zhí)行opcode,把結(jié)果存儲(chǔ)到local_retval中
...
retval = SUCCESS;
return retval;
}
先把php編譯為opcode,然后執(zhí)行這個(gè)opcode。只是這個(gè)函數(shù)有一些關(guān)鍵的結(jié)構(gòu)需要理一下。
zval
我們會(huì)看到
zval local_retval;
這樣的變量,然后會(huì)對(duì)這個(gè)變量進(jìn)行如下操作:
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是一個(gè)弱類型的語(yǔ)言,它可以用一個(gè)$var來(lái)代表string,int,array,object等。這個(gè)就是歸功于zval_struct結(jié)構(gòu)
// zval的結(jié)構(gòu)
struct _zval_struct {
zend_value value; // 存儲(chǔ)具體值,它的結(jié)構(gòu)根據(jù)類型不同而不同
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type, // 這個(gè)位置標(biāo)記了這個(gè)val是什么類型的(IS_STRING/IS_INT)
zend_uchar type_flags, // 這個(gè)位置標(biāo)記了這個(gè)val是什么屬性 (IS_CALLABLE等)
zend_uchar const_flags, // 常量的一些屬性 (IS_CONSTANT_CLASS)
zend_uchar reserved) // 保留的一些字段
} v;
uint32_t type_info; // 類型的一些額外信息
} u1; // 保存類型的一些關(guān)鍵信息
union {
uint32_t next; // 如果是在hash鏈表中,這個(gè)指針代表下一個(gè)元素的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; // 一些附屬字段
};
這個(gè)接口最重要的兩個(gè)字段是 value,存儲(chǔ)變量的值。另一個(gè)是u1.v.type 存儲(chǔ)變量的`類型。這里,value也是一個(gè)結(jié)構(gòu)
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實(shí)體
zend_function *func; // 函數(shù)實(shí)體
struct {
uint32_t w1;
uint32_t w2;
} ww;
} zend_value;
如果u1.v.type == IS_STRING, 那么value.str就是指向了zend_string結(jié)構(gòu)。好了,php的垃圾回收是通過(guò)引用計(jì)數(shù)來(lái)進(jìn)行的,這個(gè)引用計(jì)數(shù)的計(jì)數(shù)器就放在zval.value.counted里面。
我們對(duì)zval設(shè)置的時(shí)候設(shè)置了一些宏來(lái)進(jìn)行設(shè)置,比如:ZVAL_STRINGL是設(shè)置string,我們仔細(xì)看下調(diào)用堆棧:
ZVAL_STRINGL(&pv, str, str_len); // 把pv設(shè)置為string類型,值為str
這個(gè)函數(shù)就是把pv設(shè)置為zend_string類型
// 帶字符串長(zhǎng)度的設(shè)置zend_sting類型的zval
#define ZVAL_STRINGL(z, s, l) do { \
ZVAL_NEW_STR(z, zend_string_init(s, l, 0)); \
} while (0)
注意到,這里使用了一個(gè)寫(xiě)法,do {} while(0) 來(lái)設(shè)置一個(gè)宏,這個(gè)是C里面比較好的寫(xiě)法,這樣寫(xiě),能保證宏中定義的東西在for,if,等各種流程語(yǔ)句中不會(huì)出現(xiàn)語(yǔ)法錯(cuò)誤。不過(guò)其實(shí)我們學(xué)習(xí)代碼的時(shí)候,可以忽略掉這個(gè)框框?qū)懛ā?/p>
zend_string_init(s, l, 0)
...
// 從char* + 長(zhǎng)度 + 是否是臨時(shí)變量(persistent為0表示最遲這個(gè)申請(qǐng)的空間在請(qǐng)求結(jié)束的時(shí)候就進(jìn)行釋放),轉(zhuǎn)變?yōu)閦end_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); // 申請(qǐng)空間,申請(qǐng)的大小為zend_string結(jié)構(gòu)大小(除了val)+ len + 1
memcpy(ZSTR_VAL(ret), str, len);
ZSTR_VAL(ret)[len] = '\0';
return ret;
}
這個(gè)函數(shù)可以看的點(diǎn)有幾個(gè):
persistent
這個(gè)參數(shù)是用來(lái)代表申請(qǐng)的空間是不是“臨時(shí)”的。這里說(shuō)的臨時(shí)是zend提供的一種內(nèi)存管理器,相關(guān)請(qǐng)求數(shù)據(jù)只服務(wù)于單個(gè)請(qǐng)求,最遲會(huì)在請(qǐng)求結(jié)束的時(shí)候釋放。
臨時(shí)內(nèi)存申請(qǐng)對(duì)應(yīng)的函數(shù)為:
void *emalloc(size_t size)
而永久內(nèi)存申請(qǐng)對(duì)應(yīng)的函數(shù)為:
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的結(jié)構(gòu):
// 字符串
struct _zend_string {
zend_refcounted_h gc; // gc使用的被引用的次數(shù)
zend_ulong h; // 如果這個(gè)字符串作為hashtable的key在查找時(shí)候需要重復(fù)計(jì)算它的hash值,所以保存一份在這里
size_t len; // 字符串長(zhǎng)度
char val[1]; // 柔性數(shù)組,雖然我們定義了數(shù)組只有一個(gè)元素,但是在實(shí)際分配內(nèi)存的時(shí)候,會(huì)分配足夠的內(nèi)存
};
_ZSTR_STRUCT_SIZE(len) gc+h+len的空間,最后給了val留了len+1的長(zhǎng)度
#define _ZSTR_STRUCT_SIZE(len) (_ZSTR_HEADER_SIZE + len + 1)
## GC_REFCOUNT(ret) = 1;
#define GC_REFCOUNT(p) (p)->gc.refcount
這里就看到一個(gè)結(jié)構(gòu)zend_refcounted_h
typedef struct _zend_refcounted_h {
uint32_t refcount; // 真正的計(jì)數(shù)
union {
struct {
ZEND_ENDIAN_LOHI_3(
zend_uchar type, // 冗余了zval中的類型值
zend_uchar flags, // used for strings & objects中有特定作用
uint16_t gc_info) // 在GC緩沖區(qū)中的.索引位置
} v;
uint32_t type_info; // 冗余zval中的type_info
} u; // 類型信息
} zend_refcounted_h;
回到我們的實(shí)例,我們調(diào)用的是
zend_string_init(s, l, 0) // s=char*(echo 12;) l=8
返回的zend_string實(shí)際值為:
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
};
結(jié)合到zval里面,那么ZVAL_STRINGL(&pv, str, str_len);返回的zval為
// zval的結(jié)構(gòu)
struct _zval_struct {
union _zend_value {
zend_long lval;
double dval;
zend_refcounted *counted;
zend_string *str; // 指向到上面定義的那個(gè)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;
};
這里,就對(duì)zval結(jié)構(gòu)有初步了解了。
另外建議記住幾個(gè)常用的類型,后續(xù)調(diào)試的時(shí)候會(huì)很有用
/* 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
【php內(nèi)核分析之zval】相關(guān)文章:
php內(nèi)核分析之sapi-module-struct10-17
php內(nèi)核分析之ZTS和zend-try07-18
PHP遞歸效率分析08-25
PHP與ASP的分析對(duì)比10-27
分析PHP隊(duì)列是什么10-05
php中引用的用法分析06-22
PHP頁(yè)面漏洞分析的方法08-13