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:php

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

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

     從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中php7

 

字符串結構數據結構

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長度申請內存

     字符串分類:tcp

             IS_STR_PERSISTENT(經過malloc分配的)函數

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

            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把白色的幾點刪除  清除這些垃圾

相關文章
相關標籤/搜索