摘要:这里阅读的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的内容。