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 ...
随机推荐
- 了不起的 nodejs-TwitterWeb 案例 bug 解决
了不起的nodejs算是一本不错的入门书,不过书中个别案例存在bug,按照书中源码无法做出和书中相同效果,原本兴奋的心情掺杂着些许失落. 现在我们看一下第七章HTTP,一个Twitter Web客户端 ...
- Node.js:进程、子进程与cluster多核处理模块
1.process对象 process对象就是处理与进程相关信息的全局对象,不需要require引用,且是EventEmitter的实例. 获取进程信息 process对象提供了很多的API来获取当前 ...
- [C#] 走进 LINQ 的世界
走进 LINQ 的世界 序 在此之前曾发表过三篇关于 LINQ 的随笔: 进阶:<LINQ 标准查询操作概述>(强烈推荐) 技巧:<Linq To Objects - 如何操作字符串 ...
- SQL Server2016升级前几点自检
SQL Server2016已经出来一段时间了,而且最新的SP1包也于2016年11月18日正式发布,各种新的特性推出让我们跃跃欲试.那么对于我们真实的业务环境,特别是生产环境要不要"跟风& ...
- 【踩坑速记】二次依赖?android studio编译运行各种踩坑解决方案,杜绝弯路,总有你想要的~
这篇博客,只是把自己在开发中经常遇到的打包编译问题以及解决方案给大家稍微分享一下,不求吸睛,但求有用. 1.大家都知道我们常常会遇到dex超出方法数的问题,所以很多人都会采用android.suppo ...
- javascript高性能编程-算法和流程控制
代码整体结构是执行速度的决定因素之一. 代码量少不一定运行速度快, 代码量多也不一定运行速度慢. 性能损失与代码组织方式和具体问题解决办法直接相关. 倒序循环可以提高性能,如: ...
- 常用 meta 整理
<!-- 针对手持设备优化,主要是针对一些老的不识别viewport的浏览器,比如黑莓 --> <meta name="HandheldFriendly" con ...
- Visual Studio 2013 添加一般应用程序(.ashx)文件到SharePoint项目
默认,在用vs2013开发SharePoint项目时,vs没有提供一般应用程序(.ashx)的项目模板,本文解决此问题. 以管理员身份启动vs2013,创建一个"SharePoint 201 ...
- Android之三种网络请求解析数据(最佳案例)
AsyncTask解析数据 AsyncTask主要用来更新UI线程,比较耗时的操作可以在AsyncTask中使用. AsyncTask是个抽象类,使用时需要继承这个类,然后调用execute()方法. ...
- EChart系列:在echart3中使用百度地图扩展之后,如何获取到百度地图对象
最近做项目想要在百度地图上叠加显示echart的散点图,然后根据地图的缩放等级和区域范围要显示不同的散点图,这中间折腾了好久.功能要求包括: (1)底图使用百度地图: (2)可以在地图上叠加显示ech ...