资讯专栏INFORMATION COLUMN

php7内核阅读(1)--数据容器zval和zend_value

canger / 3006人阅读

摘要:本文主要是针对,的话可以移步到庆哥的博客看,还有就是小菜我读的是内核剖析这本书。接下来我会使用到来调试源码本文有参照博客中的部分内容以及代码。

前言

工作+实习快一年了,搞php后端开发,一直很迷茫怎么提高自己,就先从php源码开始吧,本人比较菜,本文章写的比较赶时间,所以有什么错误或者漏掉的地方,望各位大神指正,多交流才能成长嘛,嘿嘿。
本文主要是针对php7,php5的话可以移步到庆哥的博客看,还有就是小菜我读的是《php7内核剖析》这本书。
接下来我会使用到xdebug来调试php源码

本文有参照ohmygirl博客中的部分内容以及代码。

本文所用环境为windows,php7.0.10

php7中zval,zend_value的基本结构

php7和php5不同的地方有很多,zval,zend_value结构就是其中之一

在php7中

zval定义在zend_types.h中

在zval这个结构体重包含三个部分 zend_value(存储实际的内容),u1,u2两个联合体,其中u1主要存储变量相关的一下属性,而u2则是对u1的一些补充,例如当用到数组的时候,会用到u2.next来解决key哈希后出现的hash冲突

struct _zval_struct {
    zend_value        value;            /* 存储变量的实际内容 */
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                zend_uchar    type,            /* 存储变量的类型 */
                zend_uchar    type_flags,  /* 用于标识变量状态,例如GC方面的管理,通过设置为IS_TYPE_COLLECTABLE 则变量会被收集到GC中回收垃圾的buffer缓存区中 */
                zend_uchar    const_flags,
                zend_uchar    reserved)        /* call info for EX(This) */
        } v;
        uint32_t type_info;
    } u1;
    union {
        uint32_t     next;                 /* hash collision chain */
        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 */
        uint32_t     extra;                /* not further specified */
    } u2;  
};

zend_uchar type: 以下为外部使用的变量类型

#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

php7中zend_value结构

typedef union _zend_value {
    zend_long         lval;                /* long value */
    double            dval;                /* double value */
    zend_refcounted  *counted;          /*用于统计计数用,*/
    zend_string      *str;
    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;
} zend_value;

这里我们先解释一下php7的zval,zend_valu中重要的的几个变量

zval中:

(1)zend_uchar    type 这个是用来表示当前变量(例如 $a)是什么类型的变量($a是string类型?还是int类型?还是引用类型....)

zend_value中:

(1)zend_refcounted  *counted; 表示引用计数的次数,
何为引用计数?
就是zend_value变量被zval引用的次数,例如我们$a=“abcs”;$c=$a;$b=$a;此时counted=3,如下图,其中refcount也是实现GC自动内存回收的基础,下面会详细讲解

php7变量的内部实现

php7中对与变量的实现分以下几种方式


(1).对于boolen类型,还有null,undefined,这种没有具体值,只有类型的类型,直接在zval中通过zend_uchar type的类型来判断,无需通过引用计数来实现。

正是因为没有通过应用计数来实现,所以它refcount为0

(2)对于int类型和float类型,因为在zend_value中有zend_long和double来保存数据,如下图,所以,在赋值的时候就不需要再使用引用计数了,在拷贝的直接进行赋值就行了,这样做可以省掉大量引用计数的相关操作

定义一个$a=1,在php内核中zval和zend_value的关系

(3)第三种就是常规的使用引用计数的方式来进行来进行变量的定义。在这些变量的实现中,都是通过指针指向一个具体的数据类型

例如 :
    zend_string  *str;
    struct _zend_string {
        zend_refcounted_h gc;
        zend_ulong        h;                /* hash value */
        size_t            len;
        char              val[1];
    };

$假设我定义一个$a="111";其内部的实现就是,注意这里zend_string中char val[1],主要是因为C语言中字符串是以“0”结尾的,所以在zend_string中$a="111"这个变量值的存储是char val[4]="111"



php7中赋值

1.普通赋值
前面说到,在php中,定义一个变量$a="444",实际上是生成了一个zval,和一个zend_value,然后zval指向这个zend_value来实现对$a="444"的定义的,然后通过refcount来统计引用的次数。

**所以可以总结出,refcount表示当前有多少个zval指向同一个zend_value**

我们定义如下:

$a="111";
$b=$a;

当然对于像boole型还有int,double,null变量,他们的直接通过zval保存,不会公用一个zend_value,所以直接使用深拷贝。
至于什么是深拷贝,什么是浅拷贝,最直接的区别就是在于有没有重新生成一个一模一样的zend_value,详细请参看深拷贝和浅拷贝。
后面的写时赋值(COW copy on write)就会使用到深拷贝。

对于php的赋值,实际上并不是所有的类型都是一样的,刚刚也有说到,在php的zval中就有一个专门的字段用于标识当前类型适合哪种形式的那就是。

    zend_uchar    type_flags,

                | refcounted | collectable | copyable | immutable
----------------+------------+-------------+----------+----------
simple types    |            |             |          |
string          |      x     |             |     x    |
interned string |            |             |          |
array           |      x     |      x      |     x    |
immutable array |            |             |          |     x
object          |      x     |      x      |          |
resource        |      x     |             |          |
reference       |      x     |             |          |

zend_uchar type_flags这个字段用于标识当前的zval的属于哪种赋值方式或者处于哪种状态,主要是用于内存方面的管理具体参见内存管理,

2.引用赋值

php中的引用赋值就是我们常用的引用,例如$a=&$b,这样。

在php7中实现引用的时候,在php中实现引用的时候,会先生成一个zend_reference类型,这个类型中嵌套一个zval,然后这个zval的zend_value会指向之前的之前的那个zval的zend_value,原来的那个zval类型转换成IS_REFERENCE类型,简单来说**就是将原有的zval类型转换成IS_REFERENCE,并新生成zend_reference类型指向原zend的zend_value。(好jb绕,理了好久)。

struct _zend_reference {
    zend_refcounted_h gc;
    zval              val;
};

例如我们定义一个$a="1111";$b=&$a;它的变换如下图
$a="111"

$b=&$a;

可能有朋友注意到了,在zend_reference中refcount为2 ,但是在最后的真实的zend_value为refcount为1,这是为什么呢,其实很好理解,前面说过,refcount表示有多少个zval指向该zend_value,zend_reference中只有一个zval指向了zend_value。
中间加一层zend_reference这样做其实有很多好处,这样可以保留原有的zend_value类型不变,为深拷贝操作提供拷贝条件,下面我们举个例子就知道了

是不是一目了然,只能说开发内核的那些神牛太牛逼了。。。。。。。

当然关于php中值传递不仅仅那么简单,还有很多很复杂的东西,小菜我也是才看没多久,望各位大牛勿喷

php中的COW (copy on write)

写时复制是一种很重要的优化手段,这里涉及到深拷贝和浅拷贝的知识,请移步。

关于深拷贝,刚刚上面的这个例子就是很好的说明

所谓深拷贝就是将原有的数据拷贝一份放到独立分配一个地址空间。
而写时赋值就是对深拷贝的一种优化吧,意思是只有当发生写操作的时候才进行深拷贝

举个例子:

$a="3333";
$b=$a;

$b.="444";

$a="3333";
$b=$a;
这个操作会使两个zval指向同一个zend_value
这里并没有触发COW,执行深拷贝

当$b.="444";发生了写操作的时候,触发COW,执行深拷贝,拷贝了完全一样的一份zend_value,$b所在的zval由原来的和$a所在的zval共同指向之前的zend_value, 转换成指向拷贝出的新的zend_value

如果不进行深拷贝的话,那么当执行$b.="444";后,$a也会等于“3333444”

当然不是所有的zend_value类型都可以进行复制,这个请参见我之前的那个zend_uchar type_flags表

**文章中可能有些不足的地方,恳求指正。
本来打算再写写GC回收的原理的,但是现在已经2点多了,23333333.明天还要继续打码呢。。。。。。。不好意思**

下一篇准备写一下php中的数组的实现;

lift needs art,i need girl


本文参考

http://bbs.csdn.net/topics/39...

http://blog.csdn.net/black_ox...
http://blog.csdn.net/xiaolei1...
https://segmentfault.com/a/11...
https://github.com/laruence/p...
https://www.cnblogs.com/ohmyg...

可能有遗漏的参考博客,望博主见谅

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/28175.html

相关文章

  • php内核阅读(2)--浅谈 gc回收机制

    摘要:垃圾回收所谓垃圾就是指通过循环引用自己引用自己,目前只在类型中有出现的形式而导致永远不为。当出现垃圾之后,的引擎有对应的垃圾回收机制。触发这个机制的时机是每次出现减少时候。 自嘲)。。。。。2333,我觉得这是因为在php语言层面就帮我们解决了内存回收的问题,但这让我在和java大牛们吹牛逼的时候,听到什么内存泄露。。。。(纳尼,我tmd怎么从来没遇见过)一脸懵逼。 本人小菜,如果下面...

    wemallshop 评论0 收藏0
  • (PHP7内核剖析-3) 变量

    摘要:插入一个元素时先将元素按先后顺序插入数组,位置是,再根据的哈希值映射到散列表中的某个位置,将存入这个位置查找时先在散列表中映射到,得到在数组的位置,再从数组中取出元素。目前只有两种类型会使用这种机制。 1.变量结构 typedef struct _zval_struct zval; typedef union _zend_value { zend_long ...

    RiverLi 评论0 收藏0
  • PHP7扩展开发(三):参数、数组Zvals

    摘要:告诉引擎要取的参数的信息,用来确保线程安全,返回值检测是还是。数组遍历假设我们需要一个取代以下功能的扩展的遍历数组和差很多,提供了一些专门的宏来遍历元素或。是一个关于线程安全的动作,用于避免各线程的作用域被其他的侵入。 起步 到这已经能声明简单函数,返回静态或者动态值了。定义INI选项,声明内部数值或全局数值。本章节将介绍如何接收从调用脚本(php文件)传入参数的数值,以及 PHP内核...

    hufeng 评论0 收藏0
  • PHP7源码分析】如何理解PHP虚拟机(一)

    摘要:操作数本身并无数据类型,它的数据类型由操作码确定任何架构的计算机都会对外提供指令集合运算器通过执行指令直接发出控制信号控制计算机各项操作。 顺风车运营研发团队 李乐 1.从物理机说起 虚拟机也是计算机,设计思想和物理机有很多相似之处; 1.1冯诺依曼体系结构 冯·诺依曼是当之无愧的数字计算机之父,当前计算机都采用的是冯诺依曼体系结构;设计思想主要包含以下几个方面: 指令和数据不加区别...

    tunny 评论0 收藏0
  • 彻底搞懂 PHP 变量结构体,多数文章观点不准确

    摘要:中的用于类型整型和资源类型用于浮点类型用于字符串用于数组用于对象用于常量表达式才有多数文章,在提到变量结构体的时候,都提到,实际上这个论述并不准确,在为时,这个结果是正确的。主要看中的,是两个,这个永远是个字节,所以,因此。 PHP5 中的 zval // 1. zval typedef struct _zval_struct { zvalue_value value; ...

    mtunique 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<