亚洲国产日韩欧美一区二区三区,精品亚洲国产成人av在线,国产99视频精品免视看7,99国产精品久久久久久久成人热,欧美日韩亚洲国产综合乱

characters

變量的內(nèi)部實現(xiàn)

變量是一個語言實現(xiàn)的基礎(chǔ),變量有兩個組成部分:變量名、變量值,PHP中可以將其對應(yīng)為:zval、zend_value,這兩個概念一定要區(qū)分開,PHP中變量的內(nèi)存是通過引用計數(shù)進行管理的,而且PHP7中引用計數(shù)是在zend_value而不是zval上,變量之間的傳遞、賦值通常也是針對zend_value。

PHP中可以通過$關(guān)鍵詞定義一個變量:$a;,在定義的同時可以進行初始化:$a = "hi~";,注意這實際是兩步:定義、初始化,只定義一個變量也是可以的,可以不給它賦值,比如:

$a;
$b = 1;

這段代碼在執(zhí)行時會分配兩個zval。

接下來我們具體看下變量的結(jié)構(gòu)以及不同類型的實現(xiàn)。

變量的基礎(chǔ)結(jié)構(gòu)

//zend_types.htypedef struct _zval_struct     zval;typedef union _zend_value {
    zend_long         lval;    //int整形
    double            dval;    //浮點型
    zend_refcounted  *counted;
    zend_string      *str;     //string字符串
    zend_array       *arr;     //array數(shù)組
    zend_object      *obj;     //object對象
    zend_resource    *res;     //resource資源類型
    zend_reference   *ref;     //引用類型,通過&$var_name定義的
    zend_ast_ref     *ast;     //下面幾個都是內(nèi)核使用的value
    zval             *zv;    void             *ptr;
    zend_class_entry *ce;
    zend_function    *func;    struct {
        uint32_t w1;        uint32_t w2;
    } ww;
} zend_value;struct _zval_struct {
    zend_value        value; //變量實際的value
    union {        struct {
            ZEND_ENDIAN_LOHI_4( //這個是為了兼容大小字節(jié)序,小字節(jié)序就是下面的順序,大字節(jié)序則下面4個順序翻轉(zhuǎn)
                zend_uchar    type,         //變量類型
                zend_uchar    type_flags,  //類型掩碼,不同的類型會有不同的幾種屬性,內(nèi)存管理會用到
                zend_uchar    const_flags,
                zend_uchar    reserved)     //call info,zend執(zhí)行流程會用到
        } v;        uint32_t type_info; //上面4個值的組合值,可以直接根據(jù)type_info取到4個對應(yīng)位置的值
    } u1;    union {        uint32_t     var_flags;        uint32_t     next;                 //哈希表中解決哈希沖突時用到
        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 */
    } u2; //一些輔助值};

zval結(jié)構(gòu)比較簡單,內(nèi)嵌一個union類型的zend_value保存具體變量類型的值或指針,zval中還有兩個union:u1、u2:

u1: 它的意義比較直觀,變量的類型就通過u1.v.type區(qū)分,另外一個值type_flags為類型掩碼,在變量的內(nèi)存管理、gc機制中會用到,第三部分會詳細分析,至于后面兩個const_flags、reserved暫且不管

u2: 這個值純粹是個輔助值,假如zval只有:value、u1兩個值,整個zval的大小也會對齊到16byte,既然不管有沒有u2大小都是16byte,把多余的4byte拿出來用于一些特殊用途還是很劃算的,比如next在哈希表解決哈希沖突時會用到,還有fe_pos在foreach會用到......

從zend_value可以看出,除long、double類型直接存儲值外,其它類型都為指針,指向各自的結(jié)構(gòu)。

類型

zval.u1.type類型:

/* 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
/* fake types */
#define _IS_BOOL                    13
#define IS_CALLABLE                 14
/* internal types */
#define IS_INDIRECT                 15
#define IS_PTR                      17

標量類型

最簡單的類型是true、false、long、double、null,其中true、false、null沒有value,直接根據(jù)type區(qū)分,而long、double的值則直接存在value中:zend_long、double,也就是標量類型不需要額外的value指針。

字符串

PHP中字符串通過zend_string表示:

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

gc: 變量引用信息,比如當前value的引用數(shù),所有用到引用計數(shù)的變量類型都會有這個結(jié)構(gòu),3.1節(jié)會詳細分析

h: 哈希值,數(shù)組中計算索引時會用到

len: 字符串長度,通過這個值保證二進制安全

val: 字符串內(nèi)容,變長struct,分配時按len長度申請內(nèi)存

事實上字符串又可具體分為幾類:IS_STR_PERSISTENT(通過malloc分配的)、IS_STR_INTERNED(php代碼里寫的一些字面量,比如函數(shù)名、變量值)、IS_STR_PERMANENT(永久值,生命周期大于request)、IS_STR_CONSTANT(常量)、IS_STR_CONSTANT_UNQUALIFIED,這個信息通過flag保存:zval.value->gc.u.flags,后面用到的時候再具體分析。

數(shù)組

array是PHP中非常強大的一個數(shù)據(jù)結(jié)構(gòu),它的底層實現(xiàn)就是普通的有序HashTable,這里簡單看下它的結(jié)構(gòu),下一節(jié)會單獨分析數(shù)組的實現(xiàn)。

typedef struct _zend_array HashTable;struct _zend_array {
    zend_refcounted_h gc; //引用計數(shù)信息,與字符串相同
    union {        struct {
            ZEND_ENDIAN_LOHI_4(
                zend_uchar    flags,
                zend_uchar    nApplyCount,
                zend_uchar    nIteratorsCount,
                zend_uchar    reserve)
        } v;        uint32_t flags;
    } u;    uint32_t          nTableMask; //計算bucket索引時的掩碼
    Bucket           *arData; //bucket數(shù)組
    uint32_t          nNumUsed; //已用bucket數(shù)
    uint32_t          nNumOfElements; //已有元素數(shù),nNumOfElements <= nNumUsed,因為刪除的并不是直接從arData中移除
    uint32_t          nTableSize; //數(shù)組的大小,為2^n
    uint32_t          nInternalPointer; //數(shù)值索引
    zend_long         nNextFreeElement;    dtor_func_t       pDestructor;
};

對象/資源

struct _zend_object {
    zend_refcounted_h gc;    uint32_t          handle;
    zend_class_entry *ce; //對象對應(yīng)的class類
    const zend_object_handlers *handlers;
    HashTable        *properties; //對象屬性哈希表
    zval              properties_table[1];
};struct _zend_resource {
    zend_refcounted_h gc;    int               handle;    int               type;    void             *ptr;
};

對象比較常見,資源指的是tcp連接、文件句柄等等類型,這種類型比較靈活,可以隨意定義struct,通過ptr指向,后面會單獨分析這種類型,這里不再多說。

引用

引用是PHP中比較特殊的一種類型,它實際是指向另外一個PHP變量,對它的修改會直接改動實際指向的zval,可以簡單的理解為C中的指針,在PHP中通過&操作符產(chǎn)生一個引用變量,也就是說不管以前的類型是什么,&首先會創(chuàng)建一個zend_reference結(jié)構(gòu),其內(nèi)嵌了一個zval,這個zval的value指向原來zval的value(如果是布爾、整形、浮點則直接復(fù)制原來的值),然后將原zval的類型修改為IS_REFERENCE,原zval的value指向新創(chuàng)建的zend_reference結(jié)構(gòu)。

struct _zend_reference {
    zend_refcounted_h gc;
    zval              val;
};

結(jié)構(gòu)非常簡單,除了公共部分zend_refcounted_h外只有一個val,舉個示例看下具體的結(jié)構(gòu)關(guān)系:

$a = "time:" . time();      //$a    -> zend_string_1(refcount=1)$b = &$a;                   //$a,$b -> zend_reference_1(refcount=2) -> zend_string_1(refcount=1)

最終的結(jié)果如圖:



注意:引用只能通過&產(chǎn)生,無法通過賦值傳遞,比如:

$a = "time:" . time();      //$a    -> zend_string_1(refcount=1)
$b = &$a;                   //$a,$b -> zend_reference_1(refcount=2) -> zend_string_1(refcount=1)
$c = $b;                    //$a,$b -> zend_reference_1(refcount=2) -> zend_string_1(refcount=2)
                            //$c    ->

$b = &$a這時候$a、$b的類型是引用,但是$c = $b并不會直接將$b賦值給$c,而是把$b實際指向的zval賦值給$c,如果想要$c也是一個引用則需要這么操作:

$a = "time:" . time();      //$a       -> zend_string_1(refcount=1)
$b = &$a;                   //$a,$b    -> zend_reference_1(refcount=2) -> zend_string_1(refcount=1)
$c = &$b;/*或$c = &$a*/     //$a,$b,$c -> zend_reference_1(refcount=3) -> zend_string_1(refcount=1)

這個也表示PHP中的 引用只可能有一層 ,不會出現(xiàn)一個引用指向另外一個引用的情況 ,也就是沒有C語言中指針的指針的概念。

內(nèi)存管理

接下來分析下變量的分配、銷毀。

在分析變量內(nèi)存管理之前我們先自己想一下可能的實現(xiàn)方案,最簡單的處理方式:定義變量時alloc一個zval及對應(yīng)的value結(jié)構(gòu)(ref/arr/str/res...),賦值、函數(shù)傳參時硬拷貝一個副本,這樣各變量最終的值完全都是獨立的,不會出現(xiàn)多個變量同時共用一個value的情況,在執(zhí)行完以后直接將各變量及value結(jié)構(gòu)free掉。

這種方式是可行的,而且內(nèi)存管理也很簡單,但是,硬拷貝帶來的一個問題是效率低,比如我們定義了一個變量然后賦值給另外一個變量,可能后面都只是只讀操作,假如硬拷貝的話就會有多余的一份數(shù)據(jù),這個問題的解決方案是: 引用計數(shù)+寫時復(fù)制 。PHP變量的管理正是基于這兩點實現(xiàn)的。

引用計數(shù)

引用計數(shù)是指在value中增加一個字段refcount記錄指向當前value的數(shù)量,變量復(fù)制、函數(shù)傳參時并不直接硬拷貝一份value數(shù)據(jù),而是將refcount++,變量銷毀時將refcount--,等到refcount減為0時表示已經(jīng)沒有變量引用這個value,將它銷毀即可。

$a = "time:" . time();   //$a       ->  zend_string_1(refcount=1)$b = $a;                 //$a,$b    ->  zend_string_1(refcount=2)$c = $b;                 //$a,$b,$c ->  zend_string_1(refcount=3)unset($b);               //$b = IS_UNDEF  $a,$c ->  zend_string_1(refcount=2)

引用計數(shù)的信息位于給具體value結(jié)構(gòu)的gc中:

typedef struct _zend_refcounted_h {
    uint32_t         refcount;          /* reference counter 32-bit */
    union {        struct {
            ZEND_ENDIAN_LOHI_3(
                zend_uchar    type,
                zend_uchar    flags,    /* used for strings & objects */
                uint16_t      gc_info)  /* keeps GC root number (or 0) and color */
        } v;        uint32_t type_info;
    } u;
} zend_refcounted_h;

從上面的zend_value結(jié)構(gòu)可以看出并不是所有的數(shù)據(jù)類型都會用到引用計數(shù),long、double直接都是硬拷貝,只有value是指針的那幾種類型才__可能__會用到引用計數(shù)。

下面再看一個例子:

$a = "hi~";
$b = $a;

猜測一下變量$a/$b的引用情況。

這個不跟上面的例子一樣嗎?字符串"hi~"有$a/$b兩個引用,所以zend_string1(refcount=2)。但是這是錯的,gdb調(diào)試發(fā)現(xiàn)上面例子zend_string的引用計數(shù)為0。這是為什么呢?

$a,$b -> zend_string_1(refcount=0,val="hi~")

事實上并不是所有的PHP變量都會用到引用計數(shù),標量:true/false/double/long/null是硬拷貝自然不需要這種機制,但是除了這幾個還有兩個特殊的類型也不會用到:interned string(內(nèi)部字符串,就是上面提到的字符串flag:IS_STR_INTERNED)、immutable array,它們的type是IS_STRING、IS_ARRAY,與普通string、array類型相同,那怎么區(qū)分一個value是否支持引用計數(shù)呢?還記得zval.u1中那個類型掩碼type_flag嗎?正是通過這個字段標識的,這個字段除了標識value是否支持引用計數(shù)外還有其它幾個標識位,按位分割,注意:type_flag與zval.value->gc.u.flag不是一個值。

支持引用計數(shù)的value類型其zval.u1.type_flag包含 (注意是&,不是等于)IS_TYPE_REFCOUNTED:

#define IS_TYPE_REFCOUNTED          (1<<2)

下面具體列下哪些類型會有這個標識:

|     type       | refcounted |
+----------------+------------+
|simple types    |            |
|string          |      Y     |
|interned string |            |
|array           |      Y     |
|immutable array |            |
|object          |      Y     |
|resource        |      Y     |
|reference       |      Y     |

simple types很顯然用不到,不再解釋,string、array、object、resource、reference有引用計數(shù)機制也很容易理解,下面具體解釋下另外兩個特殊的類型:

interned string: 內(nèi)部字符串,這是種什么類型?我們在PHP中寫的所有字符都可以認為是這種類型,比如function name、class name、variable name、靜態(tài)字符串等等,我們這樣定義:$a = "hi~";后面的字符串內(nèi)容是唯一不變的,這些字符串等同于C語言中定義在靜態(tài)變量區(qū)的字符串:char *a = "hi~";,這些字符串的生命周期為request期間,request完成后會統(tǒng)一銷毀釋放,自然也就無需在運行期間通過引用計數(shù)管理內(nèi)存。

immutable array: 只有在用opcache的時候才會用到這種類型,不清楚具體實現(xiàn),暫時忽略。

寫時復(fù)制

上一小節(jié)介紹了引用計數(shù),多個變量可能指向同一個value,然后通過refcount統(tǒng)計引用數(shù),這時候如果其中一個變量試圖更改value的內(nèi)容則會重新拷貝一份value修改,同時斷開舊的指向,寫時復(fù)制的機制在計算機系統(tǒng)中有非常廣的應(yīng)用,它只有在必要的時候(寫)才會發(fā)生硬拷貝,可以很好的提高效率,下面從示例看下:

$a = array(1,2);
$b = &$a;
$c = $a;//發(fā)生分離$b[] = 3;

最終的結(jié)果:

不是所有類型都可以copy的,比如對象、資源,實時上只有string、array兩種支持,與引用計數(shù)相同,也是通過zval.u1.type_flag標識value是否可復(fù)制的:

#define IS_TYPE_COPYABLE         (1<<4)
|     type       |  copyable  |
+----------------+------------+
|simple types    |            |
|string          |      Y     |
|interned string |            |
|array           |      Y     |
|immutable array |            |
|object          |            |
|resource        |            |
|reference       |            |

copyable 的意思是當value發(fā)生duplication時是否需要或者能夠copy,這個具體有兩種情形下會發(fā)生:

a.從 literal變量區(qū) 復(fù)制到 局部變量區(qū) ,比如:$a = [];實際會有兩個數(shù)組,而$a = "hi~";//interned string則只有一個string

b.局部變量區(qū)分離時(寫時復(fù)制):如改變變量內(nèi)容時引用計數(shù)大于1則需要分離,$a = [];$b = $a; $b[] = 1;這里會分離,類型是array所以可以復(fù)制,如果是對象:

$a = new user;$b = $a;$a->name = "dd";

這種情況是不會復(fù)制object的,$a、$b指向的對象還是同一個

變量回收

PHP變量的回收主要有兩種:主動銷毀、自動銷毀。主動銷毀指的就是 unset ,而自動銷毀就是PHP的自動管理機制,在return時減掉局部變量的refcount,即使沒有顯式的return,PHP也會自動給加上這個操作,另外一個就是寫時復(fù)制時會斷開原來value的指向,這時候也會檢查斷開后舊value的refcount。

垃圾回收

PHP變量的回收是根據(jù)refcount實現(xiàn)的,當unset、return時會將變量的引用計數(shù)減掉,如果refcount減到0則直接釋放value,這是變量的簡單gc過程,但是實際過程中出現(xiàn)gc無法回收導(dǎo)致內(nèi)存泄漏的bug,先看下一個例子:

$a = [1];
$a[] = &$a;unset($a);

unset($a)之前引用關(guān)系:


unset($a)之后:

可以看到,unset($a)之后由于數(shù)組中有子元素指向$a,所以refcount > 0,無法通過簡單的gc機制回收,這種變量就是垃圾,垃圾回收器要處理的就是這種情況,目前垃圾只會出現(xiàn)在array、object兩種類型中,所以只會針對這兩種情況作特殊處理:當銷毀一個變量時,如果發(fā)現(xiàn)減掉refcount后仍然大于0,且類型是IS_ARRAY、IS_OBJECT則將此value放入gc可能垃圾雙向鏈表中,等這個鏈表達到一定數(shù)量后啟動檢查程序?qū)⑺凶兞繖z查一遍,如果確定是垃圾則銷毀釋放。

標識變量是否需要回收也是通過u1.type_flag區(qū)分的:

#define IS_TYPE_COLLECTABLE
|     type       | collectable |
+----------------+-------------+
|simple types    |             |
|string          |             |
|interned string |             |
|array           |      Y      |
|immutable array |             |
|object          |      Y      |
|resource        |             |
|reference       |             |

具體的垃圾回收過程這里不再介紹,后面會單獨分析。

Previous article: Next article: