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/yjf512/p/6108444.html,如需转载请自行联系原作者

相关文章
|
1月前
|
存储 缓存 自然语言处理
深入PHP内核:理解Opcode缓存与性能优化
【5月更文挑战第14天】 在动态语言的世界里,PHP一直因其高性能的执行效率和广泛的社区支持而备受青睐。随着Web应用的复杂性增加,对性能的要求也越来越高。本文将探讨PHP的Opcode缓存机制,解析其对性能提升的贡献,并展示如何通过配置和使用不同的Opcode缓存方案来进一步优化PHP应用的性能。我们将深入到PHP的核心,了解Opcode是如何生成的,以及它如何影响最终的执行效率。
|
17天前
|
存储 缓存 自然语言处理
深入PHP内核:Opcode缓存与性能优化
【5月更文挑战第31天】 在提升PHP应用性能的众多策略中,Opcode缓存技术以其显著的性能提升效果而广受关注。本文旨在深入探讨PHP的Opcode缓存机制,解析其对性能提升的影响,并讨论如何通过配置和优化实践来充分利用Opcode缓存。文章将首先介绍Opcode的概念及其在PHP执行过程中的作用,然后分析几种流行的Opcode缓存解决方案,最后提供针对性的优化建议,帮助开发者实现高效的PHP应用。
|
17天前
|
存储 缓存 PHP
深入PHP内核:理解Zend Engine与Opcode缓存
【5月更文挑战第30天】 在PHP的开发世界中,性能优化是一个永恒的话题。随着现代Web应用的复杂性日益增加,仅仅依靠代码层面的优化已经远远不够。本文将深入探讨PHP的执行心脏——Zend Engine,以及如何通过Opcode缓存机制提升PHP应用的执行效率。我们将透过对Zend Engine工作原理的分析,了解Opcode缓存的实现原理,并通过实例来展示其对性能提升的显著影响。
|
21天前
|
缓存 算法 PHP
深入PHP内核:探索高性能代码优化策略
【5月更文挑战第27天】 在本文中,我们将深入探讨PHP的内核机制,并分析如何通过理解其工作原理来优化我们的代码。文章将聚焦于几个关键领域,包括解释器执行流程、内存管理、以及代码编写最佳实践等。我们的目标是为开发者提供实用的指导,帮助他们写出更加高效和稳定的PHP应用程序。
|
27天前
|
存储 缓存 负载均衡
深入PHP内核:探索Opcode缓存对性能的影响
在现代Web开发中,提升应用性能始终是开发者追求的目标之一。PHP作为一种广泛使用的服务端脚本语言,其执行效率对网站性能有着直接的影响。本文将深入探讨PHP的Opcode缓存机制,分析Opcode缓存如何优化PHP代码执行流程,减少服务器资源消耗,并通过实验数据展示启用Opcode缓存对性能的具体影响。我们将比较不同的Opcode缓存方案,并讨论它们在实际项目中的适用场景与潜在限制。
|
1月前
|
存储 缓存 自然语言处理
深入PHP内核:理解OPcache的工作原理与优化实践
【5月更文挑战第6天】 在现代Web开发中,提升性能和响应速度是持续追求的目标。PHP作为一种广泛使用的服务端脚本语言,其执行效率至关重要。本文将深入探索PHP的OPcache(优化器缓存)组件,解析其如何改善PHP的性能表现。通过剖析OPcache的工作机制,我们将讨论有效的配置策略以及实践中的最佳优化方法,旨在帮助开发者充分理解并利用OPcache来提升应用性能。
|
1月前
|
存储 监控 安全
PHP医院安全(不良)事件报告系统源码 vue2+element支持11大类不良事件上报、审核处理、分析改进
医院安全(不良)事件管理系统采用无责的、自愿的填报不良事件方式,有效地减轻医护人员的思想压力,实现以事件为主要对象,可以自动、及时、实际地反应医院的安全、不良、近失事件的情况,更好地掌握不良事件的发生趋势,为及时采取适当的管理措施和流程、制度改进提供了良好的量化依据。系统通过汇集不同类型事件的报告,从中分析出医院内部潜在的问题和风险,将发生的事故降到最低,从而保证病人安全和医护人员安全。
28 0
|
1月前
|
数据库连接 PHP
深入PHP内核:理解Zend Engine和PHP生命周期
【5月更文挑战第4天】 在本文中,我们将探讨PHP的核心——Zend Engine。我们会详细解释Zend Engine的工作原理,以及它如何驱动PHP代码的执行。此外,我们还将深入讨论PHP生命周期的各个阶段,包括从请求开始到输出结果的整个过程。这篇文章将为你提供一个深入的理解,关于PHP如何处理你的代码,以及它在背后是如何运作的。
43 3
|
1月前
|
存储 缓存 自然语言处理
深入PHP内核:探索Opcode缓存机制
【5月更文挑战第1天】 在动态语言如PHP的执行过程中,每次脚本被请求时都需要经过一系列复杂的解析和编译步骤。为了优化这一过程并提高性能,PHP引入了Opcode缓存机制。本文将详细探讨Opcode的概念、作用以及它如何显著提升PHP应用的执行效率。我们将从缓存原理出发,分析几种常见的Opcode缓存工具,并通过实例说明如何在实际项目中实现和优化缓存策略。
|
1月前
|
缓存 自然语言处理 监控
深入PHP内核:探索高性能脚本编程的秘密
【4月更文挑战第30天】 在现代Web开发中,PHP作为一种流行的服务器端脚本语言,其性能优化一直是开发者关注的焦点。本文将深入探讨PHP内核架构,分析影响PHP脚本性能的关键因素,并提出一系列提升执行效率的策略。我们将从语言解释器的角度出发,剖析词法分析、语法分析和执行机制,同时考虑内存管理和代码优化的实践技巧。通过本文的阅读,读者能够对PHP的性能调优有更深层次的理解,并在实际项目中运用这些知识以实现高效的脚本运行。