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
php内核分析(五)-zval的更多相关文章
- Linux内核分析第五周学习总结:扒开系统调用的三层皮(下)
韩玉琪 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.给MenuO ...
- Linux内核分析(五)----字符设备驱动实现
原文:Linux内核分析(五)----字符设备驱动实现 Linux内核分析(五) 昨天我们对linux内核的子系统进行简单的认识,今天我们正式进入驱动的开发,我们今后的学习为了避免大家没有硬件的缺陷, ...
- 《Linux内核分析》第五周学习总结
<Linux内核分析>第五周学习总结 ——扒开系统调用的三层皮(下) 姓名:王玮怡 学号:20135116 1.给menu ...
- 《Linux内核分析》第五周学习笔记
<Linux内核分析>第五周学习笔记 扒开系统调用的三层皮(下) 郭垚 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.c ...
- LINUX内核分析第五周学习总结——扒开系统调用的“三层皮”(下)
LINUX内核分析第五周学习总结--扒开系统调用的"三层皮"(下) 标签(空格分隔): 20135321余佳源 余佳源 原创作品转载请注明出处 <Linux内核分析>M ...
- 《Linux 内核分析》第五周
[李行之原创作品 转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] <Linux内 ...
- LINUX内核分析第五周学习总结——扒开应用系统的三层皮(下)
LINUX内核分析第五周学习总结——扒开应用系统的三层皮(下) 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/cou ...
- 《Linux内核分析》 第五节 扒开系统调用的三层皮(下)
<Linux内核分析> 第五节 扒开系统调用的三层皮(下) 20135307 一.给MenusOS增加time和time-asm命令 给MenuOS增加time和time-asm命令需要 ...
- Linux内核分析——第五周学习笔记
第五周 扒开系统调用的“三层皮”(下) 一.知识点总结 (一)给MenuOS增加time和time-asm命令 在实验楼中,首先 强制删除menu (rm menu -rf) 重新克隆一个新版本的me ...
- 20135327郭皓--Linux内核分析第五周 扒开系统调用的三层皮(下)
Linux内核分析第五周 扒开系统调用的三层皮(下) 郭皓 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/U ...
随机推荐
- Socket聊天程序——初始设计
写在前面: 可能是临近期末了,各种课程设计接踵而来,最近在csdn上看到2个一样问答(问题A,问题B),那就是编写一个基于socket的聊天程序,正好最近刚用socket做了一些事,出于兴趣,自己抽了 ...
- eclipse 快捷键大全
注:因eclipse版本.电脑配置等原因 有些快捷键可能导致不可用(遇到些许问题可在下方评论) [Ct rl+T] 搜索当前接口的实现类 1. [ALT +/] 此快捷键为用户编辑的好帮手,能为 ...
- document.documentElement.clientHeight 与 document.body.clientHeight(杜绝千篇一律的抄袭!!)
document.documentElement.clientHeight 与 document.body.clientHeight用来获取页面可视高度我觉得有点问题.这两个应该不是一个东西. 页面中 ...
- Spring框架概述
Spring是最流行的Java企业级应用开发框架,全球数以百万的开发者在使用Spring框架创建高性能.易测试.可重用的代码. Spring框架的核心特性可以应用于任何Java应用,但扩展的JavaE ...
- WebAPi之SelfHost自创建证书启动Https疑难解惑及无法正确返回结果
前言 话说又来需求了,之前对于在SelfHost中需要嵌套页面并操作为非正常需求,这回来正常需求了,客户端现在加了https,老大过来说WebAPi访问不了了,这是什么情况,我去试了试,还真是这个情况 ...
- .Net 分布式云平台基础服务建设说明概要
1) 背景 建设云平台的基础框架,用于支持各类云服务的业务的构建及发展. 2) 基础服务 根据目前对业务的理解和发展方向,总结抽象出以下几个基础服务,如图所示 3) 概要说明 基础服务的发展会根 ...
- System.Guid ToString五中格式
参考:https://msdn.microsoft.com/en-us/library/97af8hh4.aspx 测试代码: using System; using System.Collectio ...
- 浏览器的兼容模式下的button中文字垂直方向不居中显示
<button style="cursor:pointer;vertical-align: middle;" >删除</button> 这时候垂直不居中. ...
- Android(安卓)-------CardView
1.activity_main.xml <android.support.v7.widget.CardView android:id="@+id/cardView" andr ...
- Android之SAX解析XML
一.SAX解析方法介绍 SAX(Simple API for XML)是一个解析速度快并且占用内存少的XML解析器,非常适合用于Android等移动设备. SAX解析器是一种基于事件的解析器,事件驱动 ...