首頁  >  文章  >  後端開發  >  淺談PHP原始碼三十:PHP記憶體池中的儲存層

淺談PHP原始碼三十:PHP記憶體池中的儲存層

不言
不言原創
2018-06-29 09:41:411461瀏覽

這篇文章主要介紹了關於淺談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 getscan pendent ”, “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_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,
__inDWORD 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_SIZEED;如果如果;沒有設定沒使用預設的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:试着访问不属于进程的内存区

以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP中文网!

相关推荐:

浅谈PHP源码二十九:关于接口的继承

浅谈PHP源码二十八:关于类结构和继承

浅谈PHP源码二十七:PHP对构造方法的识别

以上是淺談PHP原始碼三十:PHP記憶體池中的儲存層的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn