內存池

內存池

(Memory Pool)是一種內存分配方式。通常我們習慣直接使用new、malloc等API申請分配內存,這樣做的缺點在於:由於所申請內存塊的大小不定,當頻繁使用時會造成大量的內存碎片並進而降低性能。

基本概念


內存池則是在真正使用內存之前,先申請分配一定數量的、大小相等(一般情況下)的內存塊留作備用。當有新的內存需求時,就從內存池中分出一部分內存塊,若內存塊不夠再繼續申請新的內存。這樣做的一個顯著優點是,使得內存分配效率得到提升。
在內核中有不少地方內存分配不允許失敗. 作為一個在這些情況下確保分配的方式, 內核開發者創建了一個已知為內存池(或者是 "mempool" )的抽象. 一個內存池真實地只是一類後備緩存, 它儘力一直保持一個空閑內存列表給緊急時使用.
一個內存池有一個類型 mempool_t ( 在 中定義); 你可以使用 mempool_create 創建一個:
mempool_t *mempool_create(int min_nr, mempool_alloc_t *alloc_fn, mempool_free_t *free_fn, void *pool_data); min_nr 參數是內存池應當一直保留的最小數量的分配的對象. 實際的分配和釋放對象由 alloc_fn 和 free_fn 處理, 它們有這些原型:
typedef void *(mempool_alloc_t)(int gfp_mask, void *pool_data); typedef void (mempool_free_t)(void *element, void *pool_data); 給 mempool_create 最後的參數 ( pool_data ) 被傳遞給 alloc_fn 和 free_fn.
如果需要, 你可編寫特殊用途的函數來處理 mempool 的內存分配. 常常, 但是, 你只需要使內核 slab 分配器為你處理這個任務. 有 2 個函數 ( mempool_alloc_slab 和 mempool_free_slab) 來進行在內存池分配原型和 kmem_cache_alloc 和 kmem_cache_free 之間的感應淬火. 因此, 設置內存池的代碼常常看來如此:
cache = kmem_cache_create(. . .); pool = mempool_create(MY_POOL_MINIMUM,mempool_alloc_slab, mempool_free_slab, cache); 一旦已創建了內存池, 可以分配和釋放對象,使用:
void *mempool_alloc(mempool_t *pool, int gfp_mask); void mempool_free(void *element, mempool_t *pool); 當內存池創建了, 分配函數將被調用足夠的次數來創建一個預先分配的對象池. 因此, 對 mempool_alloc 的調用試圖從分配函數請求額外的對象; 如果那個分配失敗, 一個預先分配的對象(如果有剩下的)被返回. 當一個對象被用 mempool_free 釋放, 它保留在池中, 如果對齊預分配的對象數目小於最小量; 否則, 它將被返回給系統.
一個 mempool 可被重新定大小, 使用:
int mempool_resize(mempool_t *pool, int new_min_nr, int gfp_mask); 這個調用, 如果成功, 調整內存池的大小至少有 new_min_nr 個對象. 如果你不再需要一個內存池, 返回給系統使用:
void mempool_destroy(mempool_t *pool); 你編寫返回所有的分配的對象, 在銷毀 mempool 之前, 否則會產生一個內核 oops.
如果你考慮在你的驅動中使用一個 mempool, 請記住一件事: mempools 分配一塊內存在一個鏈表中, 對任何真實的使用是空閑和無用的. 容易使用 mempools 消耗大量的內存. 在幾乎每個情況下, 首選的可選項是不使用 mempool 並且代替以簡單處理分配失敗的可能性. 如果你的驅動有任何方法以不危害到系統完整性的方式來響應一個分配失敗, 就這樣做. 驅動代碼中的 mempools 的使用應當少.

實現示例


內存池的實現有很多,性能和適用性也不相同,以下是一種較簡單的C++實現 -- GenericMP模板類。(本例取材於《Online game server programming》一書)
在這個例子中,使用了模板以適應不同對象的內存需求,內存池中的內存塊則是以基於鏈表的結構進行組織。
GenericMP模板類定義
template
class GenericMP
{
public:
static VOID *operator new(size_t allocLen)
{
assert(sizeof(T) == allocLen);
if(!m_NewPointer)
MyAlloc();
UCHAR *rp = m_NewPointer;
m_NewPointer = *reinterpret_cast(rp); //由於頭4個位元組被“強行”解釋為指向下一內存塊的指針,這裡m_NewPointer就指向了下一個內存塊,以備下次分配使用。
return rp;
}
static VOID operator delete(VOID *dp)
{
*reinterpret_cast(dp) = m_NewPointer;
m_NewPointer = static_cast(dp);
}
private:
static VOID MyAlloc()
{
m_NewPointer = new UCHAR[sizeof(T) * BLOCK_NUM];
UCHAR **cur = reinterpret_cast(m_NewPointer); //強制轉型為雙指針,這將改變每個內存塊頭4個位元組的含義。
UCHAR *next = m_NewPointer;
for(INT i = 0; i < BLOCK_NUM-1; i++)
{
next += sizeof(T);
*cur = next;
cur = reinterpret_cast(next); //這樣,所分配的每個內存塊的頭4個位元組就被“強行“解釋為指向下一個內存塊的指針,即形成了內存塊的鏈表結構。
}
*cur = 0;
}
static UCHAR *m_NewPointer;
protected:
~GenericMP()
{
}
};
template
UCHAR *GenericMP::m_NewPointer;
GenericMP模板類應用
class ExpMP : public GenericMP
{
BYTE a[1024];
};
int _tmain(int argc, _TCHAR* argv[])
{
ExpMP *aMP = new ExpMP();
delete aMP;
}