php變量的實現


 1.php變量的實現
變量名 zval ,變量值 zend_value,php7的變量內存管理的引用計數 在zend_value結構上,變量的操作也都是zend_value實現的。

//zend_types.h
typedef 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數組
    zend_object      *obj;     //object對象
    zend_resource    *res;     //resource資源類型
    zend_reference   *ref;     //引用類型,通過&$var_name定義的
    zend_ast_ref     *ast;     //下面幾個都是內核使用的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( //這個是為了兼容大小字節序,小字節序就是下面的順序,大字節序則下面4個順序翻轉
                zend_uchar    type,         //變量類型
                zend_uchar    type_flags,  //類型掩碼,不同的類型會有不同的幾種屬性,內存管理會用到
                zend_uchar    const_flags,
                zend_uchar    reserved)     //call info,zend執行流程會用到
        } v;
        uint32_t type_info; //上面4個值的組合值,可以直接根據type_info取到4個對應位置的值
    } 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結構比較簡單,內嵌一個union類型的zend_value保存具體變量類型的值或指針,zval中還有兩個union:u1u2:

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

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

     從zend_value可以看出,除longdouble類型直接存儲值外,其它類型都為指針,指向各自的結構。

     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,直接根據type區分,而long、double的值則直接存在value中

 

字符串結構

struct _zend_string {
    zend_refcounted_h gc;
    zend_ulong        h;                /* hash value */
    size_t            len;
    char              val[1];
};
  • gc: 變量引用信息,比如當前value的引用數,所有用到引用計數的變量類型都會有這個結構,3.1節會詳細分析
  • h: 哈希值,數組中計算索引時會用到
  • len: 字符串長度,通過這個值保證二進制安全
  • val: 字符串內容,分配時按len長度申請內存

     字符串分類:

             IS_STR_PERSISTENT(通過malloc分配的)

            IS_STR_INTERNED(php代碼里寫的一些字面量,比如函數名、變量值)

            IS_STR_PERMANENT(永久值,生命周期大於request)

            IS_STR_CONSTANT(常量)

            IS_STR_CONSTANT_UNQUALIFIED

 

數組結構   php數組的底層結構是hashTabe   (php內核的函數 類 文件索引表 全局符號表頁都是用hash結構實現的)

          hash結構根據hash碼進行訪問的key-value數據結構,根據key映射函數直接索引到value,采用直接尋址直接映射key到內存地址,查找的期望時間O(1)

typedef struct _zend_array HashTable;

struct _zend_array {
    zend_refcounted_h gc; //引用計數信息,與字符串相同
    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數組
    uint32_t          nNumUsed; //已用bucket數
    uint32_t          nNumOfElements; //已有元素數,nNumOfElements <= nNumUsed,因為刪除的並不是直接從arData中移除
    uint32_t          nTableSize; //數組的大小,為2^n
    uint32_t          nInternalPointer; //數值索引
    zend_long         nNextFreeElement;
    dtor_func_t       pDestructor;
};

             

    arData指向存儲元素數組的第一個Bucket,插入元素時按順序 依次插入 數組,比如第一個元素在arData[0]、第二個在arData[1]...arData[nNumUsed]。PHP數組的有序性正是通過arData保證的,arData並不是按key映射的散列表,列表在ht->arData內存之前,分配內存時這個散列表與Bucket數組一起分配,arData向后移動到了Bucket數組的起始位置,並不是申請內存的起始位置,這樣散列表可以由arData指針向前移動訪問到,即arData[-1]、arData[-2]、arData[-3]......散列表的結構是uint32_t,它保存的是value在Bucket數組中的位置。

                         

               

                                                    

     hash碰撞     不同的key可能計算得到相同的哈希值(數值索引的哈希值直接就是數值本身),但是這些值又需要插入同一個散列表。一般解決方法是將Bucket串成鏈表,查找時遍歷鏈表比較key。

     數組擴容      容量不夠時檢查 刪除元素的比例,達到一個閾值時重建索引,沒有達到時直接擴容為當前大小的兩倍,復制bucket到新的內存空間重建索引

     重建散列表   當刪除元素達到一定數量或擴容后都需要重建散列表,因為value在Bucket位置移動了或哈希數組nTableSize變化了導致key與value的映射關系改變,重建過程實際就是遍歷Bucket數組中的value,然后重新計算映射值更新到散列表。

     hash元素刪除 一個元素從哈希表刪除時並不會將對應的Bucket移除,而是將Bucket存儲的zval修改為IS_UNDEF,只有擴容時發現nNumOfElements與nNumUsed相差達到一定數量(這個數量是:ht->nNumUsed - ht->nNumOfElements > (ht->nNumOfElements >> 5))時才會將已刪除的元素全部移除,重新構建哈希表  。

 

對象/資源結構

struct _zend_object {
    zend_refcounted_h gc;
    uint32_t          handle;
    zend_class_entry *ce; //對象對應的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指向。

 引用類型結構

struct _zend_reference {
    zend_refcounted_h gc;
    zval              val;
};

       引用是PHP中比較特殊的一種類型,它實際是指向另外一個PHP變量,對它的修改會直接改動實際指向的zval,可以簡單的理解為C中的指針,但是PHP中的 引用只可能有一層 ,不會出現一個引用指向另外一個引用的情況 ,也就是沒有C語言中指針的指針的概念。

 

2.內存管理

          引用計數 使用的是refcount字段,變量復制和函數傳參的時候直接refcount++,銷毀變量refcount--,refcount=0銷毀變量。

                    簡單的標量數據類型不會用到引用計數直接是硬拷貝,只有value是指針的數據類型使用引用計數。

                    字符串的內部字符串類型不會用到引用計數,函數名 類名 靜態字符串 變量名等字面量都是這種數據類型

        寫時復制  

                    引用計數之后,如果其中一個變量試圖更改value的內容則會重新拷貝一份value修改,同時斷開舊的指向。

        變量回收   主動銷毀(unset)、自動銷毀()

                  PHP變量的回收是根據refcount實現的,當unset、return時會將變量的引用計數減掉,如果refcount減到0則直接釋放value,這是變量的簡單gc過程,但是實際過程中array、object兩種類型中出現gc無法回收導致內存泄漏的bug。

                 當銷毀一個變量時,如果發現減掉refcount后仍然大於0,且類型是IS_ARRAY、IS_OBJECT則將此value放入gc可能垃圾雙向鏈表(緩沖buffer)中,等這個鏈表達到一定數量(10000個值)后啟動檢查程序將所有變量檢查一遍,如果確定是垃圾則銷毀釋放。

                 遍歷 buffer鏈表,把value標記 灰色-》 深度遍歷value成員 refcount-- 標記灰色   -》 再次遍歷buffer鏈表檢查value的refcount是否是0 -》 是0表示是垃圾標記為白色,不是0表示不是垃圾 -》深度遍歷不是0的value把所有成員變量的refcount++標記為黑色 -〉遍歷buffer把白色的幾點刪除  清除這些垃圾

 

                 

        

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM