PostgreSQL 堆分配器mmgr詳解

來源:互聯網
上載者:User

mmgr是postgresql的記憶體管理模組,其代碼分布在aset.c, mctx.c和portalmem.c這三個檔案之中。這裡主要分析它的堆記憶體的管理機制,也就是aset.c檔案中的內容。

AllocSetContext是堆記憶體管理的主要結構,其定義如下:

typedef struct AllocSetContext{    MemoryContextData header;                             /* Standard memory-context fields */    /* Info about storage allocated in this context: */    AllocBlock    blocks;                                 /* head of list of blocks in this set */    AllocChunk    freelist[ALLOCSET_NUM_FREELISTS];       /* free chunk lists */    /* Allocation parameters for this context: */    Size        initBlockSize;                            /* initial block size */    Size        maxBlockSize;                             /* maximum block size */    Size        nextBlockSize;                            /* next block size to allocate */    Size        allocChunkLimit;                          /* effective chunk size limit */    AllocBlock    keeper;                                 /* if not NULL, keep this block over resets */} AllocSetContext;

其中的成員可以分為三個部分,我用三種不同的顏色標記了出來:

1) 黃色部分是AllocSetContext的頭部,如果用物件導向的觀點來看,可以說AllocSetContext是繼承自MemoryContextData的,這裡我們不關心MemoryContextData的定義,它提供了對AllocSetContext更高層次的管理。

2)粉色部分是一些配置參數。

3) 紫色部分是我們這篇部落格的主角。blocks和freelist對應於堆記憶體管理的兩個方面。下面詳細描述。

AllocSetContext作為malloc/free與更上層應用的中介層,向下需要管理使用malloc從作業系統申請到的記憶體,對上則需要提供記憶體的擷取與釋放的介面。我們先說第一個方面,也就是它是如何管理malloc來的記憶體的。

AllocSetContext將從作業系統中申請到的記憶體放在一下鏈表中進行管理,這個鏈表就是紫色部分的blocks。blocks中的每一項的結構叫做AllocBlockData,它的定義如下:

typedef struct AllocBlockData
{
    AllocSet    aset;           
/* aset that owns this block */
    AllocBlock    next;         
/* next block in aset's blocks list */
    char       *freeptr;         /* start of free space in this block */
    char       *endptr;          /* end of space in this block */
}   AllocBlockData

AllocBlockData是向作業系統申請和釋放記憶體的基本單位,為了簡便起見,下面就直接叫它Block了。在接收到記憶體配置請求的時候,AllocSetContext如果發現blocks頭部的那個Block沒有足夠的空間,就再從作業系統中申請一個更大的Block作為blocks的頭。以後的分配出的記憶體就是從這個更大的Block上割出來的。這個更大是多大呢,pg將它設為前一個的2倍,也就是說blocks這個鏈表從尾部到頭部,每向前一個Block的大小就會增長一倍。這裡說的是通常情況,也會有非通常情況的,比如這個Block是不可能無限制地增大的,它有一個最大的可以分配的大小AllocSetContext::maxBlockSize,也還有來自其它方面的限制,要瞭解這個限制就得先說一說 AllocSetContext的另外一個主要功能了--對外提供擷取記憶體和釋放記憶體的介面。

      AllocSetContext提供給外部使用的主要介面為:

?
static void *AllocSetAlloc(MemoryContext context, Size size); static void AllocSetFree(MemoryContext context, void *pointer); static void *AllocSetRealloc(MemoryContext context, void *pointer, Size size);

  你可以調用AllocSetAlloc(cxt,sizeof(XX)) 來獲得一塊可用的特定大小的記憶體,但是如果你天真地以為你需要的是15個位元組的空間,它就會精確地給你15個位元組,你就錯了,實際上,為了你的這次內在請求,它給了你16個位元組的空間,外加一個資訊頭。也就是說它給你返回的記憶體有統一的結構,大小也有特定地限制。

      這個統一的記憶體結構的定義如下:

?
typedef struct AllocChunkData {     /* aset is the owning aset if allocated, or the freelist link if free */    void       *aset;     /* size is always the size of the usable space in the chunk */    Size        size; #ifdef MEMORY_CONTEXT_CHECKING     /* when debugging memory usage, also store actual requested size */    /* this is zero in a free chunk */    Size        requested_size; #endif }   AllocChunkData;

   AllocChunkData是返回記憶體的基本單位,下面叫它Chunk。如果你申請到一個大小為16位元組的記憶體,你實際拿到是像的這麼個東西,返回給你的地址前面是有一個Chunk頭的:

       為了加快分配的速度和方便管理,每個Chunk裡的可用空間都是2的整數次冪。同時還維護了一個叫做freelist的數組,這個數組的每一項都是一個Chunk的隊列,每個隊列內的Chunk大小都是一樣的。下面是某時刻free list的記憶體布局圖:

       這個free list的Chunk的來源有兩個方面:

       1) 調用AllocSetFree回收來的Chunk;

       2) 調用AllocSetAlloc時,如果發現blocks鏈表的第一個Block沒有足夠的空間,這時候會分配一個更大的Block,但是在做這樣事之前,首先要將頭上那個Block中還剩下的空間酌情格式化為一些Chunk加入到free list這中。

       由於這個free list數組的大小是固定的,所以在free list之中最大的Chunk的可用空間也是有限的,如果申請比最大的Chunk所能提供的空間還要大的記憶體時,就會被認為這是在申請一塊大記憶體,會直接從作業系統中malloc出來給你,這種記憶體對這個堆管理器來說只是個過客,free時是不會加入到free list中的,也就是說這種不會被堆管理器直接重利用。

 

       好了, 堆記憶體的管理的基本結構資料結構就介紹這麼多,是時候看看幾個主要方法的實現了,這裡只說一下幾個最常用 的介面: AllocSetAlloc , AllocSetFree, AllocSetRealloc

         

       下面這個圖解釋了AllocSetAlloc的流程:

      1) 首先判斷是不是在申請大記憶體?如果是轉入2),否則轉入3);

      2) 直接malloc出大小為: size 加上Block和Chunk頭的大小,調整好指標位置,返回;

      3) 計算出實際分配的記憶體大小,這個大小是比size大的最小的2^k;

      4)  free list中是否有這種大小的Chunk? 若有轉入5),否則轉入6);

      5)  從free list中取出一個滿足條件的Chunk, 返回;

      6) blocks鏈表的第一個Block是不是足夠的空間,若有轉入9),否則繼續

      7) 將第一個Block中剩餘的空間格式化為一些Chunk,加入到free list中;

      8) malloc出一個更大的Block,加入到blocks中,

      9) 從Block中格式化一個滿足條件的Chunk,返回。

 

      AllocSetFree的操作比較的簡單,如,其步驟為:

       1) 根據傳入的指標算出Chunk頭的位置 ( p - sizeof(AllocChunkData));

       2) 根據Chunk中的size資訊判斷是不是大記憶體,若不是,轉入4),否則繼續;

       3) 直接free掉(是個過客,不再利用)返回。

       4) 將Chunk入到合適的free list之中。

 

       AllocSetRealloc的步驟如下:

         1)  調整指標p的位置得到Chunk頭的地址;

         2)  如果再分配的大小比Chunk的要小,直接返回; 

         3) 如果是大記憶體,則直接調用realloc分配一個更大的Block,並更新blocks中對應的項(所有的Block在使用期間都被blocks管理著), 然後返回.

         4) 否則則先調用 AllocSetFree,再調用 AllocSetAlloc.

 

       這個堆管理器的最主要的功能和實現就敘述完了,最後看一下它提供的更進階的一些調試功能:

       1) CLOBBER_FREED_MEMORY   野指標對任何一個C/C++程式員來說都是豺狼虎豹,如果定義了這個宏,在面對野指標的時候你會從容不少,因為這個宏啟用了一項功能:AllocSetFree的記憶體都會被置為0x7f, 這樣在你第一次使用野指標的時候就會立馬發現它。

       2) MEMORY_CONTEXT_CHECKING  由於在大多數情況下,mmgr返回的記憶體大小大於申請大小的最小的2次冪。所以記憶體或多或少總是有浪費的。但這也是提高記憶體配置所帶來必然的損失,為了心理平衡一下,我們可以利用這多出來的記憶體,做其他的事,比如這個宏就會啟用這樣一項功能:在實際大小後面填充一0x7e,這樣就可以檢測是否有記憶體越界的事情發生了。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.