这里阅读的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

php内核分析(五)-zval的更多相关文章

  1. Linux内核分析第五周学习总结:扒开系统调用的三层皮(下)

    韩玉琪 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.给MenuO ...

  2. Linux内核分析(五)----字符设备驱动实现

    原文:Linux内核分析(五)----字符设备驱动实现 Linux内核分析(五) 昨天我们对linux内核的子系统进行简单的认识,今天我们正式进入驱动的开发,我们今后的学习为了避免大家没有硬件的缺陷, ...

  3. 《Linux内核分析》第五周学习总结

    <Linux内核分析>第五周学习总结                                  ——扒开系统调用的三层皮(下) 姓名:王玮怡 学号:20135116 1.给menu ...

  4. 《Linux内核分析》第五周学习笔记

    <Linux内核分析>第五周学习笔记 扒开系统调用的三层皮(下) 郭垚 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.c ...

  5. LINUX内核分析第五周学习总结——扒开系统调用的“三层皮”(下)

    LINUX内核分析第五周学习总结--扒开系统调用的"三层皮"(下) 标签(空格分隔): 20135321余佳源 余佳源 原创作品转载请注明出处 <Linux内核分析>M ...

  6. 《Linux 内核分析》第五周

    [李行之原创作品 转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] <Linux内 ...

  7. LINUX内核分析第五周学习总结——扒开应用系统的三层皮(下)

    LINUX内核分析第五周学习总结——扒开应用系统的三层皮(下) 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/cou ...

  8. 《Linux内核分析》 第五节 扒开系统调用的三层皮(下)

    <Linux内核分析> 第五节 扒开系统调用的三层皮(下) 20135307 一.给MenusOS增加time和time-asm命令 给MenuOS增加time和time-asm命令需要 ...

  9. Linux内核分析——第五周学习笔记

    第五周 扒开系统调用的“三层皮”(下) 一.知识点总结 (一)给MenuOS增加time和time-asm命令 在实验楼中,首先 强制删除menu (rm menu -rf) 重新克隆一个新版本的me ...

  10. 20135327郭皓--Linux内核分析第五周 扒开系统调用的三层皮(下)

    Linux内核分析第五周 扒开系统调用的三层皮(下) 郭皓 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/U ...

随机推荐

  1. nodejs中获取时间戳、时间差

    Nodejs中获取时间戳的方法有很多种,例如: new Date().getTime() Date.now() process.uptime() process.hrtime() 平时想获取一个时间戳 ...

  2. 【原创经验分享】WCF之消息队列

    最近都在鼓捣这个WCF,因为看到说WCF比WebService功能要强大许多,另外也看了一些公司的招聘信息,貌似一些中.高级的程序员招聘,都有提及到WCF这一块,所以,自己也关心关心一下,虽然目前工作 ...

  3. BPM配置故事之案例11-操作外部数据源

    小明:可以获取ERP数据了-- 老李:哦,这么快?小伙子,我非常看好你,来来,别急着走,再陪我聊会-- 小明:--您老人家不是又要改流程吧? 老李:没有没有,哎嘿嘿嘿,我们这不都是为公司效率着想嘛,这 ...

  4. 用Kotlin创建第一个Android项目(KAD 01)

    原文标题:Create your first Android project using Kotlin (KAD 01) 作者:Antonio Leiva 时间:Nov 21, 2016 原文链接:h ...

  5. CentOS7下自定义目录安装mono+jexus教程

    一.阅读前须知: 1.本文属于安装完Centos7之后的步骤 2.如果还不了解mono,请点击mono 3.本篇主要内容是使用自定义目录安装mono+jexus教程,使用默认目录请查看使用默认目录安装 ...

  6. 解决Windows 8.1中所有的应用(Modern App)无法打开(闪退)的问题

    我已经在3台电脑上遇到这个问题了,症状是,所有应用商店安装的App都无法打开,包括应用商店本身,在开始界面点击应用以后,应用的Logo一闪而过,然后就消失了,回到了开始界面.查看系统应用日志,会有这样 ...

  7. ZooKeeper简介

    本文中,我们将对ZooKeeper进行介绍.简单地说,ZooKeeper是一个用来在构成应用的各个子服务之间进行协调的一个服务. 由于其本身并没有特别复杂的机制,因此我们将会把更多的笔墨集中在如何对Z ...

  8. To Java程序员:切勿用普通for循环遍历LinkedList

    ArrayList与LinkedList的普通for循环遍历 对于大部分Java程序员朋友们来说,可能平时使用得最多的List就是ArrayList,对于ArrayList的遍历,一般用如下写法: p ...

  9. smartcrop.js智能图片裁剪库

    今天将为大家介绍一款近期github上很不错的开源库 – smartcrop.js.它是一款图片处理的智能裁剪库.在很多项目开发中,经常会遇见上传图片的场景,它可能是用户照片信息,也可能是商品图片等. ...

  10. CSS3变形记(上):千变万化的Div

    传统上,css就是用来对网页进行布局和渲染网页样式的.然而,css3的出现彻底打破了这一格局.了解过css3的人都知道,css3不但可以对网页进行布局和渲染样式,还可以绘制一些图形.对元素进行2D和3 ...