php內核分析---內存管理(一)

內存是計算機非常關鍵的部件之一,是暫時存儲程序以及數據的空間,CPU只有有限的寄存器可以用於 存儲計算數據,而大部分的數據都是存儲在內存中的,程序運行都是在內存中進行的。和CPU計算能力一樣, 內存也是決定計算效率的一個關鍵部分。

計算中的資源中主要包含:CPU計算能力,內存資源以及I/O。現代計算機爲了充分利用資源, 而出現了多任務操作系統,通過進程調度來共享CPU計算資源,通過虛擬存儲來分享內存存儲能力。 本章的內存管理中不會介紹操作系統級別的虛擬存儲技術,而是關注在應用層面: 如何高效的利用有限的內存資源。


目前除了使用C/C++等這類的低層編程語言以外,很多編程語言都將內存管理移到了語言之後, 例如Java, 各種腳本語言:PHP/Python/Ruby等等,程序手動維護內存的成本非常大, 而這些腳本語言或新型語言都專注於特定領域,這樣能將程序員從內存管理中解放出來專注於業務的實現。 雖然程序員不需要手動維護內存,而在程序運行過程中內存的使用還是要進行管理的, 內存管理的工作也就編程語言實現程序員的工作了。


內存管理的主要工作是儘可能高效的利用內存。


內存的使用操作包括申請內存,銷燬內存,修改內存的大小等。 如果申請了內存在使用完後沒有及時釋放則可能會造成內存泄露,如果這種情況出現在常駐程序中, 久而久之,程序會把機器的內存耗光。所以對於類似於PHP這樣沒有低層內存管理的語言來說, 內存管理是其至關重要的一個模塊,它在很大程度上決定了程序的執行效率。


在PHP層面來看,定義的變量、類、函數等實體在運行過程中都會涉及到內存的申請和釋放, 例如變量可能會在超出作用域後會進行銷燬,在計算過程中會產生的臨時數據等都會有內存操作, 像類對象,函數定義等數據則會在請求結束之後纔會被釋放。在這過程中合適申請內存合適釋放內存就比較關鍵了。 PHP從開始就有一套屬於自己的內存管理機制,在5.3之前使用的是經典的引用計數技術, 但引用計數存在一定的技術缺陷,在PHP5.3之後,引入了新的垃圾回收機制,至此,PHP的內存管理機制更加完善。


本章將介紹PHP語言實現中的內存管理技術實現。

應用層的內存管理

由於計算機的內存由操作系統進行管理,所以普通應用程序是無法直接對內存進行訪問的, 應用程序只能向操作系統申請內存,通常的應用也是這麼做的,在需要的時候通過類似malloc之類的庫函數 向操作系統申請內存,在一些對性能要求較高的應用場景下是需要頻繁的使用和釋放內存的, 比如Web服務器,編程語言等,由於向操作系統申請內存空間會引發系統調用, 系統調用和普通的應用層函數調用性能差別非常大,因爲系統調用會將CPU從用戶態切換到內核, 因爲涉及到物理內存的操作,只有操作系統才能進行,而這種切換的成本是非常大的, 如果頻繁的在內核態和用戶態之間切換會產生性能問題。


鑑於系統調用的開銷,一些對性能有要求的應用通常會自己在用戶態進行內存管理, 例如第一次申請稍大的內存留着備用,而使用完釋放的內存並不是馬上歸還給操作系統, 可以將內存進行復用,這樣可以避免多次的內存申請和釋放所帶來的性能消耗。

PHP不需要顯式的對內存進行管理,這些工作都由Zend引擎進行管理了。PHP內部有一個內存管理體系, 它會自動將不再使用的內存垃圾進行釋放,這部分的內容後面的小節會介紹到。

PHP中內存相關的功能特性

可能有很多的讀者碰到過類似下面的錯誤吧:
Fatal error: Allowed memory size of X bytes exhausted (tried to allocate Y bytes)
這個錯誤的信息很明確,PHP已經達到了允許使用的最大內存了,通常上來說這很有可能是我們的程序編寫的有些問題。 比如:一次性讀取超大的文件到內存中,或者出現超大的數組,或者在大循環中的沒有及時釋放掉不再使用的變量, 這些都有可能會造成內存佔用過大而被終止。
PHP默認的最大內存使用大小是32M, 如果你真的需要使用超過32M的內存可以修改php.ini配置文件的如下配置:
memory_limit = 32M
如果你無法修改php配置文件,如果你的PHP環境沒有禁用ini_set()函數,也可以動態的修改最大的內存佔用大小:
<?php
ini_set("memory_limit", "128M");
既然我們能動態的調整最大的內存佔用,那我們是否有辦法獲取目前的內存佔用情況呢?答案是肯定的。


memory_get_usage(),這個函數的作用是獲取 目前PHP腳本所用的內存大小。
memory_get_peak_usage(),這個函數的作用返回 當前腳本到目前位置所佔用的內存峯值,這樣就可能獲取到目前的腳本的內存需求情況。

單就PHP用戶空間提供的功能來說,我們似乎無法控制內存的使用,只能被動的獲取內存的佔用情況, 這樣的話我們學習內存管理有什麼用呢?
前面的章節有介紹到引用計數,函數表,符號表,常量表等。當我們明白這些信息都會佔用內存的時候, 我們可以有意的避免不必要的浪費內存,比如我們在項目中通常會使用autoload來避免一次性把不一定會使用的類 包含進來,而這些信息是會佔用內存的,如果我們及時把不再使用的變量unset掉之後可能會釋放掉它所佔用的空間,

內存管理一般會包括以下內容:

1、是否有足夠的內存供我們的程序使用;
2、如何從足夠可用的內存中獲取部分內存;
3、對於使用後的內存,是否可以將其銷燬並將其重新分配給其它程序使用。
與此對應,PHP的內容管理也包含這樣的內容,只是這些內容在ZEND內核中是以宏的形式作爲接口提供給外部使用。 後面兩個操作分別對應emalloc宏,efree宏,而第一個操作可以根據emalloc宏返回結果檢測。

PHP的內存管理可以被看作是分層(hierarchical)的。 
它分爲三層:存儲層(storage)、堆層(heap)和接口層(emalloc/efree)。 
存儲層通過 malloc()、mmap() 等函數向系統真正的申請內存,並通過 free() 函數釋放所申請的內存。 存儲層通常申請的內存塊都比較大,這裏申請的內存大並不是指storage層結構所需要的內存大, 只是堆層通過調用存儲層的分配方法時,其以大塊大塊的方式申請的內存,存儲層的作用是將內存分配的方式對堆層透明化。
 如圖6.1所示,PHP內存管理器。PHP在存儲層共有4種內存分配方案: malloc,win32,mmap_anon,mmap_zero, 默認使用malloc分配內存,如果設置了ZEND_WIN32宏,則爲windows版本,調用HeapAlloc分配內存, 剩下兩種內存方案爲匿名內存映射,並且PHP的內存方案可以通過設置環境變量來修改。

首先我們看下接口層的實現,接口層是一些宏定義,如下:
#define emalloc(size)                       _emalloc((size) ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC)
#define safe_emalloc(nmemb, size, offset)   _safe_emalloc((nmemb), (size), (offset) ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC)
#define efree(ptr)                          _efree((ptr) ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC)
#define ecalloc(nmemb, size)                _ecalloc((nmemb), (size) ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC)
#define erealloc(ptr, size)                 _erealloc((ptr), (size), 0 ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC)
#define safe_erealloc(ptr, nmemb, size, offset) _safe_erealloc((ptr), (nmemb), (size), (offset) ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC)
#define erealloc_recoverable(ptr, size)     _erealloc((ptr), (size), 1 ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC)
#define estrdup(s)                          _estrdup((s) ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC)
#define estrndup(s, length)                 _estrndup((s), (length) ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC)
#define zend_mem_block_size(ptr)            _zend_mem_block_size((ptr) TSRMLS_CC ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC)

這裏爲什麼沒有直接調用函數?因爲這些宏相當於一個接口層或中間層,定義了一個高層次的接口,使得調用更加容易 它隔離了外部調用和PHP內存管理的內部實現,實現了一種鬆耦合關係。雖然PHP不限制這些函數的使用, 但是官方文檔還是建議使用這些宏。這裏的接口層有點門面模式(facade模式)的味道。
接口層下面是PHP內存管理的核心實現,我們稱之爲heap層。 這個層控制整個PHP內存管理的過程,首先我們看這個層的結構:

/* mm block type */
typedef struct _zend_mm_block_info {
    size_t _size;   /* block的大小*/
    size_t _prev;   /* 計算前一個塊有用到*/
} zend_mm_block_info;
 
 
typedef struct _zend_mm_block {
    zend_mm_block_info info;
} zend_mm_block;
 
typedef struct _zend_mm_small_free_block {  /* 雙向鏈表 */
    zend_mm_block_info info;
    struct _zend_mm_free_block *prev_free_block;    /* 前一個塊 */
    struct _zend_mm_free_block *next_free_block;    /* 後一個塊 */
} zend_mm_small_free_block; /* 小的空閒塊*/
 
typedef struct _zend_mm_free_block {    /* 雙向鏈表 + 樹結構 */
    zend_mm_block_info info;
    struct _zend_mm_free_block *prev_free_block;    /* 前一個塊 */
    struct _zend_mm_free_block *next_free_block;    /* 後一個塊 */
 
    struct _zend_mm_free_block **parent;    /* 父結點 */
    struct _zend_mm_free_block *child[2];   /* 兩個子結點*/
} zend_mm_free_block;
 
 
 
struct _zend_mm_heap {
    int                 use_zend_alloc; /* 是否使用zend內存管理器 */
    void               *(*_malloc)(size_t); /* 內存分配函數*/
    void                (*_free)(void*);    /* 內存釋放函數*/
    void               *(*_realloc)(void*, size_t);
    size_t              free_bitmap;    /* 小塊空閒內存標識 */
    size_t              large_free_bitmap;  /* 大塊空閒內存標識*/
    size_t              block_size;     /* 一次內存分配的段大小,即ZEND_MM_SEG_SIZE指定的大小,默認爲ZEND_MM_SEG_SIZE   (256 * 1024)*/
    size_t              compact_size;   /* 壓縮操作邊界值,爲ZEND_MM_COMPACT指定大小,默認爲 2 * 1024 * 1024*/
    zend_mm_segment    *segments_list;  /* 段指針列表 */
    zend_mm_storage    *storage;    /* 所調用的存儲層 */
    size_t              real_size;  /* 堆的真實大小 */
    size_t              real_peak;  /* 堆真實大小的峯值 */
    size_t              limit;  /* 堆的內存邊界 */
    size_t              size;   /* 堆大小 */
    size_t              peak;   /* 堆大小的峯值*/
    size_t              reserve_size;   /* 備用堆大小*/
    void               *reserve;    /* 備用堆 */
    int                 overflow;   /* 內存溢出數*/
    int                 internal;
#if ZEND_MM_CACHE
    unsigned int        cached; /* 已緩存大小 */
    zend_mm_free_block *cache[ZEND_MM_NUM_BUCKETS]; /* 緩存數組/
#endif
    zend_mm_free_block *free_buckets[ZEND_MM_NUM_BUCKETS*2];    /* 小塊內存數組,相當索引的角色 */
    zend_mm_free_block *large_free_buckets[ZEND_MM_NUM_BUCKETS];    /* 大塊內存數組,相當索引的角色 */
    zend_mm_free_block *rest_buckets[2];    /* 剩餘內存數組*/
 
};
當初始化內存管理時,調用函數是zend_mm_startup。它會初始化storage層的分配方案, 初始化段大小,壓縮邊界值,並調用zend_mm_startup_ex()初始化堆層。 這裏的分配方案就是圖6.1所示的四種方案,它對應的環境變量名爲:ZEND_MM_MEM_TYPE。 這裏的初始化的段大小可以通過ZEND_MM_SEG_SIZE設置,如果沒設置這個環境變量,程序中默認爲256 * 1024。 這個值存儲在_zend_mm_heap結構的block_size字段中,將來在維護的三個列表中都沒有可用的內存中,會參考這個值的大小來申請內存的大小。

PHP中的內存管理主要工作就是維護三個列表小塊內存列表(free_buckets)大塊內存列表(large_free_buckets)剩餘內存列表(rest_buckets)。 看到bucket這個單詞是不是很熟悉?在前面我們介紹HashTable時,這就是一個重要的角色,它作爲HashTable中的一個單元角色。 在這裏,每個bucket也對應一定大小的內存塊列表,這樣的列表都包含雙向鏈表的實現。

我們可以把維護的前面兩個表看作是兩個HashTable,那麼,每個HashTable都會有自己的hash函數。 首先我們來看free_buckets列表,這個列表用來存儲小塊的內存分配,其hash函數爲:
#define ZEND_MM_BUCKET_INDEX(true_size)     ((true_size>>ZEND_MM_ALIGNMENT_LOG2)-(ZEND_MM_ALIGNED_MIN_HEADER_SIZE>>ZEND_MM_ALIGNMENT_LOG2))
假設ZEND_MM_ALIGNMENT爲8(如果沒有特殊說明,本章的ZEND_MM_ALIGNMENT的值都爲8),則ZEND_MM_ALIGNED_MIN_HEADER_SIZE=16, 若此時true_size=256,則((256>>3)-(16>>3))= 30。 當ZEND_MM_BUCKET_INDEX宏出現時,ZEND_MM_SMALL_SIZE宏一般也會同時出現, ZEND_MM_SMALL_SIZE宏的作用是判斷所申請的內存大小是否爲小塊的內存, 在上面的示例中,小於272Byte的內存爲小塊內存,則index最多隻能爲31, 這樣就保證了free_buckets不會出現數組溢出的情況。


在內存管理初始化時,PHP內核對初始化free_buckets列表。 從heap的定義我們可知free_buckets是一個數組指針,其存儲的本質是指向zend_mm_free_block結構體的指針。 開始時這些指針都沒有指向具體的元素,只是一個簡單的指針空間。 free_buckets列表在實際使用過程中只存儲指針,這些指針以兩個爲一對(即數組從0開始,兩個爲一對),分別存儲一個個雙向鏈表的頭尾指針。 其結構如圖6.2所示。

對於free_buckets列表位置的獲取,關鍵在於ZEND_MM_SMALL_FREE_BUCKET宏,宏代碼如下:
#define ZEND_MM_SMALL_FREE_BUCKET(heap, index) \
(zend_mm_free_block*) ((char*)&heap->free_buckets[index * 2] + \
    sizeof(zend_mm_free_block*) * 2 - \
    sizeof(zend_mm_small_free_block))
仔細看這個宏實現,發現在它的計算過程是取free_buckets列表的偶數位的內存地址加上 兩個指針的內存大小並減去zend_mm_small_free_block結構所佔空間的大小。 而zend_mm_free_block結構和zend_mm_small_free_block結構的差距在於兩個指針。 據此計算過程可知,ZEND_MM_SMALL_FREE_BUCKET宏會獲取free_buckets列表 index對應雙向鏈表的第一個zend_mm_free_block的prev_free_block指向的位置。 free_buckets的計算僅僅與prev_free_block指針和next_free_block指針相關, 所以free_buckets列表也僅僅需要存儲這兩個指針。


那麼,這個數組在最開始是怎樣的呢? 在初始化函數zend_mm_init中free_buckets與large_free_buckts列表一起被初始化。 如下代碼:
p = ZEND_MM_SMALL_FREE_BUCKET(heap, 0);
for (i = 0; i < ZEND_MM_NUM_BUCKETS; i++) {
    p->next_free_block = p;
    p->prev_free_block = p;
    p = (zend_mm_free_block*)((char*)p + sizeof(zend_mm_free_block*) * 2);
    heap->large_free_buckets[i] = NULL;
}
對於free_buckets列表來說,在循環中,偶數位的元素(索引從0開始)將其next_free_block和prev_free_block都指向自己, 以i=0爲例,free_buckets的第一個元素(free_buckets[0])存儲的是第二個元素(free_buckets[1])的地址, 第二個元素存儲的是第一個元素的地址。 此時將可能會想一個問題,在整個free_buckets列表沒有內容時,ZEND_MM_SMALL_FREE_BUCKET在獲取第一個zend_mm_free_block時, 此zend_mm_free_block的next_free_block元素和prev_free_block元素卻分別指向free_buckets[0]和free_buckets[1]。


在整個循環初始化過程中都沒有free_buckets數組的下標操作,它的移動是通過地址操作,以加兩個sizeof(zend_mm_free_block*)實現, 這裏的sizeof(zend_mm_free_block*)是獲取指針的大小。比如現在是在下標爲0的元素的位置, 加上兩個指針的值後,指針會指向下標爲2的地址空間,從而實現數組元素的向後移動, 也就是zend_mm_free_block->next_free_block和zend_mm_free_block->prev_free_block位置的後移。 這種不存儲zend_mm_free_block數組,僅存儲其指針的方式不可不說精妙。雖然在理解上有一些困難,但是節省了內存。


free_buckets列表使用free_bitmap標記是否該雙向鏈表已經使用過時有用。 當有新的元素需要插入到列表時,需要先根據塊的大小查找index, 查找到index後,在此index對應的雙向鏈表的頭部插入新的元素。


free_buckets列表的作用是存儲小塊內存,而與之對應的large_free_buckets列表的作用是存儲大塊的內存, 雖然large_free_buckets列表也類似於一個hash表,但是這個與前面的free_buckets列表一些區別。 它是一個集成了數組,樹型結構和雙向鏈表三種數據結構的混合體。 我們先看其數組結構,數組是一個hash映射,其hash函數爲:
#define ZEND_MM_LARGE_BUCKET_INDEX(S) zend_mm_high_bit(S)
 
 
static inline unsigned int zend_mm_high_bit(size_t _size)
{
 
..//省略若干不同環境的實現
    unsigned int n = 0;
    while (_size != 0) {
        _size = _size >> 1;
        n++;
    }
    return n-1;
}
這個hash函數用來計算size中最高位的1的比特位是多少,這點從其函數名就可以看出。 假設此時size爲512Byte,則這段內存會放在large_free_buckets列表, 512的二進制碼爲1000000000,則zend_mm_high_bit(512)計算的值爲9,則其對應的列表index爲9。 關於右移操作,這裏有一點說明:
一般來說,右移分爲邏輯右移和算術右移。邏輯位移在在左端補K個0,算術右移在左端補K個最高有效位的值。 C語言標準沒有明確定義應該使用哪種方式。對於無符號數據,右移必須是邏輯的。對於有符號的數據,則二者都可以。 但是,現實中都會默認爲算術右移。
以上的zend_mm_high_bit函數實現是節選的最後C語言部分(如果對彙編不瞭解的話,看這部分會比較容易一些)。 但是它卻是最後一種選擇,在其它環境中,如x86的處理中可以使用彙編語言BSR達到這段代碼的目的,這樣的速度會更快一些。 這個彙編語句是BSR(Bit Scan Reverse),BSR被稱爲逆向位掃描指令。 它使用方法爲: BSF dest,src,它的作用是從源操作數的的最高位向低位搜索,將遇到的第一個“1”所在的位序號存入目標寄存器。

我們通過一次列表的元素插入操作來理解列表的結果。 首先確定當前需要內存所在的數組元素位置,然後查找此內存大小所在的位置。 這個查找行爲是發生在樹型結構中,而樹型結構的位置與內存的大小有關。 其查找過程如下:

第一步 通過索引獲取樹型結構第一個結點並作爲當前結點,如果第一個結點爲空,則將內存放到第一個元素的結點位置,返回,否則轉第二步
第二步 從當前結點出發,查找下一個結點,並將其作爲當前結點
第三步 判斷當前結點內存的大小與需要分配的內存大小是否一樣 如果大小一樣則以雙向鏈表的結構將新的元素添加到結點元素的後面第一個元素的位置。否則轉四步
第四步 判斷當前結點是否爲空,如果爲空,則佔據結點位置,結束查找,否則第二步。
從以上的過程我們可以畫出large_free_buckets列表的結構如圖6.3所示:

從內存分配的過程中可以看出,內存塊查找判斷順序依次是小塊內存列表,大塊內存列表,剩餘內存列表。 在heap結構中,剩餘內存列表對應rest_buckets字段,這是一個包含兩個元素的數組, 並且也是一個雙向鏈表隊列,其中rest_buckets[0]爲隊列的頭,rest_buckets[1]爲隊列的尾。 而我們常用的插入和查找操作是針對第一個元素,即heap->rest_buckets[0], 當然,這是一個雙向鏈表隊列,隊列的頭和尾並沒有很明顯的區別。它們僅僅是作爲一種認知上的區分。 在添加內存時,如果所需要的內存塊的大小大於初始化時設置的ZEND_MM_SEG_SIZE的值(在heap結構中爲block_size字段) 與ZEND_MM_ALIGNED_SEGMENT_SIZE(等於8)和ZEND_MM_ALIGNED_HEADER_SIZE(等於8)的和的差,則會將新生成的塊插入 rest_buckts所在的雙向鏈表中,這個操作和前面的雙向鏈表操作一樣,都是從”隊列頭“插入新的元素。 此列表的結構和free_bucket類似,只是這個列表所在的數組沒有那麼多元素,也沒有相應的hash函數。


在heap層下面是存儲層,存儲層的作用是將內存分配的方式對堆層透明化,實現存儲層和heap層的分離。 在PHP的源碼中有註釋顯示相關代碼爲"Storage Manager"。 存儲層的主要結構代碼如下:
/* Heaps with user defined storage */
typedef struct _zend_mm_storage zend_mm_storage;
 
typedef struct _zend_mm_segment {
    size_t    size;
    struct _zend_mm_segment *next_segment;
} zend_mm_segment;
 
typedef struct _zend_mm_mem_handlers {
    const char *name;
    zend_mm_storage* (*init)(void *params);    //    初始化函數
    void (*dtor)(zend_mm_storage *storage);    //    析構函數
    void (*compact)(zend_mm_storage *storage);
    zend_mm_segment* (*_alloc)(zend_mm_storage *storage, size_t size);    //    內存分配函數
    zend_mm_segment* (*_realloc)(zend_mm_storage *storage, zend_mm_segment *ptr, size_t size);    //    重新分配內存函數
    void (*_free)(zend_mm_storage *storage, zend_mm_segment *ptr);    //    釋放內存函數
} zend_mm_mem_handlers;
 
struct _zend_mm_storage {
    const zend_mm_mem_handlers *handlers;    //    處理函數集
    void *data;
};
以上代碼的關鍵在於存儲層處理函數的結構體,對於不同的內存分配方案,所不同的就是內存分配的處理函數。 其中以name字段標識不同的分配方案。在圖6.1中,我們可以看到PHP在存儲層共有4種內存分配方案: malloc,win32,mmap_anon,mmap_zero默認使用malloc分配內存, 如果設置了ZEND_WIN32宏,則爲windows版本,調用HeapAlloc分配內存,剩下兩種內存方案爲匿名內存映射, 並且PHP的內存方案可以通過設置變量來修改。其官方說明如下:
The Zend MM can be tweaked using ZEND_MM_MEM_TYPE and ZEND_MM_SEG_SIZE environment
variables. Default values are “malloc” and “256K”. Dependent on target system you
can also use “mmap_anon”, “mmap_zero” and “win32″ storage managers.

在代碼中,對於這4種內存分配方案,分別對應實現了zend_mm_mem_handlers中的各個處理函數。 配合代碼的簡單說明如下:

/* 使用mmap內存映射函數分配內存 寫入時拷貝的私有映射,並且匿名映射,映射區不與任何文件關聯。*/
# define ZEND_MM_MEM_MMAP_ANON_DSC {"mmap_anon", zend_mm_mem_dummy_init, zend_mm_mem_dummy_dtor, zend_mm_mem_dummy_compact, zend_mm_mem_mmap_anon_alloc, zend_mm_mem_mmap_realloc, zend_mm_mem_mmap_free}
 
/* 使用mmap內存映射函數分配內存 寫入時拷貝的私有映射,並且映射到/dev/zero。*/
# define ZEND_MM_MEM_MMAP_ZERO_DSC {"mmap_zero", zend_mm_mem_mmap_zero_init, zend_mm_mem_mmap_zero_dtor, zend_mm_mem_dummy_compact, zend_mm_mem_mmap_zero_alloc, zend_mm_mem_mmap_realloc, zend_mm_mem_mmap_free}
 
/* 使用HeapAlloc分配內存 windows版本 關於這點,註釋中寫的是VirtualAlloc() to allocate memory,實際在程序中使用的是HeapAlloc*/
# define ZEND_MM_MEM_WIN32_DSC {"win32", zend_mm_mem_win32_init, zend_mm_mem_win32_dtor, zend_mm_mem_win32_compact, zend_mm_mem_win32_alloc, zend_mm_mem_win32_realloc, zend_mm_mem_win32_free}
 
/* 使用malloc分配內存 默認爲此種分配 如果有加ZEND_WIN32宏,則使用win32的分配方案*/
# define ZEND_MM_MEM_MALLOC_DSC {"malloc", zend_mm_mem_dummy_init, zend_mm_mem_dummy_dtor, zend_mm_mem_dummy_compact, zend_mm_mem_malloc_alloc, zend_mm_mem_malloc_realloc, zend_mm_mem_malloc_free}
 
static const zend_mm_mem_handlers mem_handlers[] = {
#ifdef HAVE_MEM_WIN32
    ZEND_MM_MEM_WIN32_DSC,
#endif
#ifdef HAVE_MEM_MALLOC
    ZEND_MM_MEM_MALLOC_DSC,
#endif
#ifdef HAVE_MEM_MMAP_ANON
    ZEND_MM_MEM_MMAP_ANON_DSC,
#endif
#ifdef HAVE_MEM_MMAP_ZERO
    ZEND_MM_MEM_MMAP_ZERO_DSC,
#endif
    {NULL, NULL, NULL, NULL, NULL, NULL}
};
假設我們使用的是win32內存方案,則在PHP編譯時,編譯器會選擇將ZEND_MM_MEM_WIN32_DSC宏所代碼的所有處理函數賦值給mem_handlers。 在之後我們調用內存分配時,將會使用此數組中對應的相關函數。當然,在指定環境變量 USE_ZEND_ALLOC 時,可用於允許在運行時選擇 malloc 或 emalloc 內存分配。 使用 malloc-type 內存分配將允許外部調試器觀察內存使用情況,而 emalloc 分配將使用 Zend 內存管理器抽象,要求進行內部調試。



發佈了13 篇原創文章 · 獲贊 14 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章