這篇文章主要介紹了關於淺談PHP源碼三十:PHP記憶體池中的儲存層,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下
淺談PHP源碼三十:PHP記憶體池中的儲存層
【概述】
PHP的記憶體管理器是分層(hierarchical)的。這個管理器共有三層:儲存層(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方案中堆記憶體配置的聲明】
windows API
函數HeapAlloc聲明如下:
WINBASEAPI__out_optHANDLEWINAPIHeapCreate(__in DWORD flOptions,__in SIZE_T dwInitialSize,__in SIZE_T dwMaximumSize); WINBASEAPIBOOLWINAPIHeapDestroy(__in HANDLE hHeap); WINBASEAPI__bcount(dwBytes)LPVOIDWINAPIHeapAlloc(__in HANDLE hHeap,__in DWORD dwFlags,__in SIZE_T dwBytes); WINBASEAPIBOOLWINAPIHeapFree(__inout HANDLE hHeap,__inDWORD dwFlags,__deref LPVOID lpMem); WINBASEAPISIZE_TWINAPIHeapSize(__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
【附錄】
功能描述:
mmap將一個檔案或者其它對象映射進記憶體。檔案被映射到多個頁上,如果檔案的大小不是所有頁的大小之和,最後一個頁不被使用的空間將會清零。munmap執行相反的操作,刪除特定地址地區的對象映射。
基於檔案的映射,在mmap和munmap執行過程的任何時刻,被對應檔的st_atime可能被更新。如果st_atime欄位在前述的情況下沒有得到更新,首次對映射區的第一個頁索引時會更新該欄位的值。用PROT_WRITE 和 MAP_SHARED標誌建立起來的檔案對應,其st_ctime 和 st_mtime
在對映射區寫入之後,但在msync()通過MS_SYNC 和 MS_ASYNC兩個標誌調用之前會被更新。
用法:
#include void *mmap(void *start, size_t length, int prot, int flags,int fd, off_t offset);int munmap(void *start, size_t length);
參數:
start:映射區的開始地址。
length:映射區的長度。
prot:期望的記憶體保護標誌,不能與檔案的開啟模式衝突。是以下的某個值,可以通過or運算合理地組合在一起
PROT_EXEC //頁內容可以被執行
PROT_READ //頁內容可以被讀取
PROT_WRITE //頁可以被寫入
PROT_NONE //頁不可訪問
flags:指定映射對象的類型,映射選項和映射頁是否可以共用。它的值可以是一個或者多個以下位的組合體
MAP_FIXED //使用指定的映射起始地址,如果由start和len參數指定的記憶體區重疊於現存的映射空間,重疊部分將會被丟棄。如果指定的起始地址不可用,操作將會失敗。並且起始地址必須落在頁的邊界上。
MAP_SHARED //與其它所有映射這個對象的進程共用映射空間。對共用區的寫入,相當於輸出到檔案。直到msync()或者munmap()被調用,檔案實際上不會被更新。
MAP_PRIVATE //建立一個寫入時拷貝的私人映射。記憶體地區的寫入不會影響到原檔案。這個標誌和以上標誌是互斥的,只能使用其中一個。
MAP_DENYWRITE //這個標誌被忽略。
MAP_EXECUTABLE //同上
MAP_NORESERVE //不要為這個映射保留交換空間。當交換空間被保留,對映射區修改的可能會得到保證。當交換空間不被保留,同時記憶體不足,對映射區的修改會引起段違例訊號。
MAP_LOCKED //鎖定映射區的頁面,從而防止頁面被交換出記憶體。
MAP_GROWSDOWN //用於堆棧,告訴核心VM系統,映射區可以向下擴充。
MAP_ANONYMOUS //匿名映射,映射區不與任何檔案關聯。
MAP_ANON //MAP_ANONYMOUS的別稱,不再被使用。
MAP_FILE //相容標誌,被忽略。
MAP_32BIT //將映射區放在進程地址空間的低2GB,MAP_FIXED指定時會被忽略。當前這個標誌只在x86-64平台上得到支援。
MAP_POPULATE //為檔案對應通過預讀的方式準備好頁表。隨後對映射區的訪問不會被頁違例阻塞。
MAP_NONBLOCK //僅和MAP_POPULATE一起使用時才有意義。不執行預讀,只為已存在於記憶體中的頁面建立頁表入口。
fd:有效檔案描述詞。如果MAP_ANONYMOUS被設定,為了相容問題,其值應為-1。
offset:被映射對象內容的起點。
返回說明:
成功執行時,mmap()返回被映射區的指標,munmap()返回0。失敗時,mmap()返回MAP_FAILED[其值為(void *)-1],munmap返回-1。errno被設為以下的某個值
EACCES:訪問出錯
EAGAIN:檔案已被鎖定,或者太多的記憶體已被鎖定
EBADF:fd不是有效檔案描述詞
EINVAL:一個或者多個參數無效
ENFILE:已達到系統對開啟檔案的限制
ENODEV:指定檔案所在的檔案系統不支援記憶體映射
ENOMEM:記憶體不足,或者進程已超出最大記憶體映射數量
EPERM:權能不足,操作不允許
ETXTBSY:已寫的方式開啟檔案,同時指定MAP_DENYWRITE標誌
SIGSEGV:試著向唯讀區寫入
SIGBUS:試著訪問不屬於進程的記憶體區
以上就是本文的全部內容,希望對大家的學習有所協助,更多相關內容請關注topic.alibabacloud.com!