實例說明PHP內存池中的存儲層的工作原理!
PHP的內存管理器分為三層:存儲層(storage)、堆(heap)層和 emalloc/efree 層。
存儲層通過 malloc()、mmap() 等函數向系統真正的申請內存,並通過free()函數釋放所申請的內存。
存儲層通常申請的內存塊都比較大,這裡申請的內存大並不是指storage層結構所需要的內存大,只是堆層通過調用存儲層的分配方法時,其以段的格式申請的內存比較大,存儲層的作用是將內存分配的方式對堆層透明化。
首先看storage層的結構:
- /* 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;
- };
內存的分配方式,調用的函數是_zend_mm_storage結構中的處理函數集,而內存是以段的形式表現的。
4種內存方案
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}
- };
關於匿名內存映射的優點
mmem_zero方案:
(SVR 4 ) /dev/zero Memory Mapping
1. 可以將偽設備 “/dev/zero” 作為參數傳遞給mmap而創建一個映射區。/dev/zero的特殊在於,對於該設備文件所有的讀操作都返回值為0的指定長度的字節流 ,任何寫入的內容都被丟棄。我們的興趣在於用它來創建映射區,用/dev/zero創建的映射區,其內容被初始為0。
2. 使用/dev/zero的優點在於,mmap創建映射區時,不需要一個時間存在的文件,偽文件 /dev/zero 就足夠了。缺點是只能用在相關進程間。相對於相關進程間的通信,使用線程間通信效率要更高一些。不管使用那種技術,對共享數據的訪問都需要進行同步。
mmem_anon方案:
(4.4 BSD) Anonymous Memory Mapping
1. 匿名內存映射與使用/dev/zero類型,都不需要真實的文件。要使用匿名映射之需要向mmap傳入MAP_ANON標志,並且fd參數置為-1。
2. 所謂匿名,指的是映射區並沒有通過fd與文件路徑名相關聯。匿名內存映射用在有血緣關系的進程間。
win32方案中堆內存分配的聲明
函數HeapAlloc聲明如下:
- WINBASEAPI
- __out_opt
- HANDLE
- WINAPI
- HeapCreate(
- __in DWord flOptions,
- __in SIZE_T dwInitialSize,
- __in SIZE_T dwMaximumSize
- );
- WINBASEAPI
- BOOL
- WINAPI
- HeapDestroy(
- __in HANDLE hHeap
- );
- WINBASEAPI
- __bcount(dwBytes)
- LPVOID
- WINAPI
- HeapAlloc(
- __in HANDLE hHeap,
- __in DWord dwFlags,
- __in SIZE_T dwBytes
- );
- WINBASEAPI
- BOOL
- WINAPI
- HeapFree(
- __inout HANDLE hHeap,
- __in DWord dwFlags,
- __deref LPVOID lpMem
- );
- WINBASEAPI
- SIZE_T
- WINAPI
- HeapSize(
- __in HANDLE hHeap,
- __in DWord dwFlags,
- __in LPCVOID lpMem
- );
◆hHeap是進程堆內存開始位置。
◆dwFlags是分配堆內存的標志。
◆dwBytes是分配堆內存的大小。
初始化
在zend_mm_startup啟動時,程序會根據配置設置內存分配方案和段分配大小,如下所示代碼:
- ZEND_API zend_mm_heap *zend_mm_startup(void)
- {
- int i;
- size_t seg_size;
- char *mem_type = getenv("ZEND_MM_MEM_TYPE");
- char *tmp;
- const zend_mm_mem_handlers *handlers;
- zend_mm_heap *heap;
- if (mem_type == NULL) {
- i = 0;
- } else {
- for (i = 0; mem_handlers[i].name; i++) {
- if (strcmp(mem_handlers[i].name, mem_type) == 0) {
- break;
- }
- }
- if (!mem_handlers[i].name) {
- fprintf(stderr, "Wrong or unsupported zend_mm storage type '%s'\n", mem_type);
- fprintf(stderr, " supported types:\n");
- for (i = 0; mem_handlers[i].name; i++) {
- fprintf(stderr, " '%s'\n", mem_handlers[i].name);
- }
- exit(255);
- }
- }
- handlers = &mem_handlers[i];
- tmp = getenv("ZEND_MM_SEG_SIZE");
- if (tmp) {
- seg_size = zend_atoi(tmp, 0);
- if (zend_mm_low_bit(seg_size) != zend_mm_high_bit(seg_size)) {
- fprintf(stderr, "ZEND_MM_SEG_SIZE must be a power of two\n");
- exit(255);
- } else if (seg_size < ZEND_MM_ALIGNED_SEGMENT_SIZE + ZEND_MM_ALIGNED_HEADER_SIZE) {
- fprintf(stderr, "ZEND_MM_SEG_SIZE is too small\n");
- exit(255);
- }
- } else {
- seg_size = ZEND_MM_SEG_SIZE;
- }
- //....代碼省略
- }
第1121~1138行遍歷整個mem_handlers數組,確認內存分配方案,如果沒有設置ZEND_MM_MEM_TYPE變量,默認使用malloc方案,如果是Windows(即ZEND_WIN32),則默認使用win32方案,如果設置了ZEND_MM_MEM_TYPE變量,則采用設置的方案。
第1140~1152行確認段分配大小,如果設置了ZEND_MM_SEG_SIZE變量,則使用設置的大小,此處會判斷所設置的大小是否滿足2的倍數,並且大於或等於ZEND_MM_ALIGNED_SEGMENT_SIZE + ZEND_MM_ALIGNED_HEADER_SIZE;如果沒有設置沒使用默認的ZEND_MM_SEG_SIZE。