php7內核閱讀(1)--數據容器zval和zend_value

前言

工做+實習快一年了,搞php後端開發,一直很迷茫怎麼提升本身,就先從php源碼開始吧,本人比較菜,本文章寫的比較趕時間,因此有什麼錯誤或者漏掉的地方,望各位大神指正,多交流才能成長嘛,嘿嘿。
本文主要是針對php7,php5的話能夠移步到慶哥的博客看,還有就是小菜我讀的是《php7內核剖析》這本書。
接下來我會使用到xdebug來調試php源碼php

本文有參照ohmygirl博客中的部份內容以及代碼。html

本文所用環境爲windows,php7.0.10node

clipboard.png

php7中zval,zend_value的基本結構

php7和php5不一樣的地方有不少,zval,zend_value結構就是其中之一git

在php7中

zval定義在zend_types.h中github

在zval這個結構體重包含三個部分 zend_value(存儲實際的內容),u1,u2兩個聯合體,其中u1主要存儲變量相關的一下屬性,而u2則是對u1的一些補充,例如當用到數組的時候,會用到u2.next來解決key哈希後出現的hash衝突segmentfault

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: 如下爲外部使用的變量類型windows

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

可能有遺漏的參考博客,望博主見諒

相關文章
相關標籤/搜索