这里阅读的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. ASP.NET Core应用针对静态文件请求的处理[2]: 条件请求与区间请求

    通过调用ApplicationBuilder的扩展方法UseStaticFiles注册的StaticFileMiddleware中间件帮助我们处理针对文件的请求.对于StaticFileMiddlew ...

  2. 9、 Struts2验证(声明式验证、自定义验证器)

    1. 什么是Struts2 验证器 一个健壮的 web 应用程序必须确保用户输入是合法.有效的. Struts2 的输入验证 基于 XWork Validation Framework 的声明式验证: ...

  3. Adaboost提升算法从原理到实践

    1.基本思想: 综合某些专家的判断,往往要比一个专家单独的判断要好.在"强可学习"和"弱科学习"的概念上来说就是我们通过对多个弱可学习的算法进行"组合 ...

  4. 模仿Linux内核kfifo实现的循环缓存

    想实现个循环缓冲区(Circular Buffer),搜了些资料多数是基于循环队列的实现方式.使用一个变量存放缓冲区中的数据长度或者空出来一个空间来判断缓冲区是否满了.偶然间看到分析Linux内核的循 ...

  5. .NET平台和C#编程的总结

    第一章   简单认识.NET框架    (1)首先我们得知道 .NET框架具有两个主要组件:公共语言进行时CLR(Common Language Runtime)和框架类库FCL(Framework ...

  6. 【从零开始学BPM,Day3】自定义表单开发

    [课程主题] 主题:5天,一起从零开始学习BPM [课程形式] 1.为期5天的短任务学习 2.每天观看一个视频,视频学习时间自由安排. [第三天课程] 1.课程概要 Step 1 软件下载:H3 BP ...

  7. Android中实现APP文本内容的分享发送与接收方法简述

    谨记(指定选择器Intent.createChooser()) 开始今天的内容前,先闲聊一下: (1)突然有一天头脑风暴,对很多问题有了新的看法和见解,迫不及待的想要分享给大家,文档已经写好了,我需要 ...

  8. Atitit.cto 与技术总监的区别

    Atitit.cto 与技术总监的区别 1. 核心区别1 2. Cto主要职责1 3. 如何提升到cto1 4. CTO五种基本的必备素质:2 5. 2 1. 核心区别 技术总监(Chief Tech ...

  9. ELK分析IIS日志

      LogStash.conf input { file { type => "iis_log" path => ["C:/inetpub/logs/LogF ...

  10. 让Mono 4在Raspberry Pi上飞

    最近公司有项目想要在树莓派上做,代替原来的工控机(我们是把工控主机当作小的主机用,一台小的工控主机最少也要600左右,而树莓派只要200多).于是,公司买了一个Raspberry Pi B+和一个Ra ...