6

前言

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

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

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

clipboard.png

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自动内存回收的基础,下面会详细讲解

clipboard.png

php7变量的内部实现

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


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

clipboard.png

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

clipboard.png

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

clipboard.png

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

clipboard.png

(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"

clipboard.png
clipboard.png


php7中赋值

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

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

我们定义如下:

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

clipboard.png

clipboard.png

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

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

    zend_uchar    type_flags,

clipboard.png

                | 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"

clipboard.png

$b=&$a;

clipboard.png

clipboard.png

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

clipboard.png

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

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

php中的COW (copy on write)

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

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

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

举个例子:

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

$b.="444";

clipboard.png

$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...

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


frankie
79 声望7 粉丝