<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                在前面的小節中我們介紹了內存管理一般會包括以下內容: 1. 是否有足夠的內存供我們的程序使用; 1. 如何從足夠可用的內存中獲取部分內存; 1. 對于使用后的內存,是否可以將其銷毀并將其重新分配給其它程序使用。 與此對應,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的內存方案可以通過設置環境變量來修改。 ![圖6.1 PHP內存管理器](http://box.kancloud.cn/2015-07-06_559a632d1c31c.jpg) 圖6.1 PHP內存管理器 首先我們看下接口層的實現,接口層是一些宏定義,如下: /* Standard wrapper macros */ #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所示。 ![圖6.2 free_buckets列表結構](http://box.kancloud.cn/2015-07-06_559a632d2ed07.jpg) 圖6.2 free_buckets列表結構 對于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所示: ![圖6.3 large_free_buckets列表結構](http://box.kancloud.cn/2015-07-06_559a632d3b249.jpg) 圖6.3 large_free_buckets列表結構 從內存分配的過程中可以看出,內存塊查找判斷順序依次是小塊內存列表,大塊內存列表,剩余內存列表。在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 內存管理器抽象,要求進行內部調試。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看