ホームページ  >  記事  >  バックエンド開発  >  PHP ソース コードの簡単な説明 30: PHP メモリ プールのストレージ層

PHP ソース コードの簡単な説明 30: PHP メモリ プールのストレージ層

不言
不言オリジナル
2018-06-29 09:41:411409ブラウズ

この記事では、主に PHP のソース コード 30 について紹介します。これは、特定の参照値を持っています。

PHP ソースコードの話 30: PHP メモリ プールのストレージ層

[概要]
PHP のメモリ マネージャーは階層構造になっています。このマネージャーには、ストレージ層、ヒープ層、emalloc/efree 層の 3 つの層があります。ストレージ層は実際には、malloc() や mmap() などの関数を通じてシステムからメモリを適用し、free() 関数を通じて要求されたメモリを解放します。ストレージ層は通常、比較的大きなメモリ ブロックに適用されます。ここで適用される大きなメモリは、ストレージ層の構造に必要なメモリを意味するものではなく、ヒープ層がストレージ層の割り当てメソッドを呼び出すときに適用されるメモリを意味します。セグメント形式ではメモリ割り当て方法が比較的大きいため、ストレージ層の役割はメモリ割り当て方法をヒープ層に対して透過的にすることです。

最初にストレージ層の構造を見てみましょう:
[構造]

  /* 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 デフォルトでは、ZEND_WIN32 マクロが設定されている場合は、malloc がメモリの割り当てに使用されます。 Windows のバージョンを変更し、HeapAlloc を呼び出してメモリを割り当てます。残りの 2 つのメモリ スキームは匿名メモリ マッピングであり、PHP のメモリ スキームは変数を設定することで変更できます。
公式の説明は次のとおりです。
Zend MM は ZEND_MM_MEM_TYPE および ZEND_MM_SEG_SIZE 環境
変数を使用して調整できます。デフォルト値は「malloc」および「256K」です。
「mmap_anon」、「mm​​ap_zero」、および「win32」ストレージ マネージャーも使用できます。

コードでは、これら 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 メモリ マッピング
1。疑似デバイス「/dev/zero」をパラメータとして mmap に渡してマッピング領域を作成することができます。 /dev/zero の特別な点は、このデバイス ファイルに対するすべての読み取り操作が値 0 を持つ指定された長さのバイト ストリームを返し、書き込まれた内容はすべて破棄されることです。私たちの関心は、これを使用してマッピング領域を作成することにあります。/dev/zero で作成されたマッピング領域の内容は 0 に初期化されます。
2. /dev/zero を使用する利点は、mmap がマッピング領域を作成するときに、常に存在するファイルを必要としないことです。擬似ファイル /dev/zero だけで十分です。欠点は、関連するプロセス間でのみ使用できることです。関連するプロセス間の通信と比較して、スレッド間通信を使用する方が効率的です。使用するテクノロジーに関係なく、共有データへのアクセスは同期する必要があります。

mmem_anon ソリューション:
(4.4 BSD) 匿名メモリ マッピング
1. 匿名メモリ マッピングと /dev/zero タイプの使用には実ファイルは必要ありません。匿名マッピングを使用するには、MAP_ANON フラグを mmap に渡し、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 スキームが使用され、ZEND_MM_MEM_TYPE の場合はデフォルトで win32 スキームが使用されます。変数が設定されている場合、設定スキームが使用されます。

1140行目から1152行目は、ZEND_MM_SEG_SIZE変数が設定されている場合、設定されたサイズが使用され、設定されたサイズが2の倍数以上かどうかを判定します。 ZEND_MM_ALIGNED_SEGMENT_SIZE ZEND_MM_ALIGNED_HEADER_SIZE に等しい; if 設定がない場合、デフォルトの ZEND_MM_SEG_SIZE は使用されません。

[付録]
関数の説明:
mmap はファイルまたは他のオブジェクトをメモリにマップします。ファイルは複数のページにマップされ、ファイルのサイズがすべてのページのサイズの合計と異なる場合、最後のページの未使用領域はクリアされます。 munmap は逆の操作を実行し、特定のアドレス範囲のオブジェクト マッピングを削除します。
ファイル マッピングに基づいて、mmap および munmap の実行中のいつでも、マップされたファイルの st_atime が更新される可能性があります。上記の状況で st_atime フィールドが更新されない場合、このフィールドの値は、マップされた領域の最初のページに初めてインデックスが付けられたときに更新されます。 PROT_WRITE フラグと MAP_SHARED フラグを使用して確立されたファイル マッピングの場合、その st_ctime と st_mtime
は、マッピング領域への書き込み後、MS_SYNC フラグと MS_ASYNC フラグを使用して msync() が呼び出される前に更新されます。 ######使用法:######

#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 ソース コードの簡単な説明 30: PHP メモリ プールのストレージ層の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。