內核中經常進行內存的分配和釋放。為了便於數據的頻繁分配和回收,通常建立一個空閑鏈表——內存池。當不使用的已分配的內存時,將其放入內存池中,而不是直接釋放掉。
Linux內核提供了slab層來管理內存的分配和釋放。頻繁分配和回收必然導致內存碎片,slab 緩存分配器通過對類似大小的對象進行緩存而提供這種功能,
從而避免了常見的碎片問題。
1.緩存
Linux 內核的緩存管理者有時稱為" slab 分配器". 因此, 它的功能和類型在<linux/slab.h> 中聲明. slab 分配器實現有一個 kmem_cache_t 類型的緩存; 使用一個
對 kmem_cache_create 的調用來創建它們:
kmem_cache_t *kmem_cache_create(const char *name, size_t size,
size_t offset,
unsigned long flags,
void (*constructor)(void *, kmem_cache_t *, unsigned long flags),
void (*destructor)(void *, kmem_cache_t *, unsignedlong flags));
這個函數創建一個新的可以駐留任意數目全部同樣大小的內存區的緩存對象, 大小由size 參數指定. name 參數和這個緩存關聯並且作為一個在追蹤問題時有用的管理信息(在 /proc/slabinfo 中);通常, 它被設置為被緩存的結構類型的名子. 這個緩存保留一個指向 name 的指針, 而不是拷貝它, 因此驅動應當傳遞一個指向在靜態存儲中的名子的指針(常常這個名子只是一個文字字串). 這個名子不能包含空格.
offset 是頁內的第一個對象的偏移; 它可被用來確保一個對被分配的對象的特殊對齊, 但是你最可能會使用 0 來請求缺省值.
flags 控制如何進行分配並且是下列標志的一個位掩碼:
SLAB_NO_REAP
設置這個標志保護緩存在系統查找內存時被削減. 設置這個標志通常是個壞主意; 重要的是避免不必要地限制內存分配器的行動自由.
SLAB_HWCACHE_ALIGN
這個標志需要每個數據對象被對齊到一個緩存行; 實際對齊依賴主機平台的緩存分布. 這個選項可以是一個好的選擇, 如果在 SMP 機器上你的緩存包含頻繁存取的項. 但是, 用來獲得緩存行對齊的填充可以浪費可觀的內存量.
SLAB_CACHE_DMA
這個標志要求每個數據對象在 DMA 內存區分配.
函數的 constructor 和 destructor 參數是可選函數( 但是如果沒有 constructor, 可能沒有 destructor,); 前者可以用來初始化新分配的對象, 后者可以用來"清理"對象在它們的內存被作為一個整體釋放回給系統之前.
你可以通過調用 kmem_cache_alloc 從緩存分配對象.
void *kmem_cache_alloc(kmem_cache_t *cache, int flags);
這里, cache 參數是你之前已經創建的緩存; flags 是你會傳遞給 kmalloc 的相同, 如果 kmem_cache_alloc 需要出去並分配更多內存(當緩存為空時)需要參考flags.
為釋放一個對象, 使用 kmem_cache_free:
void kmem_cache_free(kmem_cache_t *cache, const void *obj);
當用完這個緩存, 典型地當模塊被卸載, 它應當如下釋放它的緩存:
int kmem_cache_destroy(kmem_cache_t *cache);
2.內存池
在內核中有不少地方內存分配不允許失敗. 作為一個在這些情況下確保分配的方式, 內核開發者創建了一個已知為內存池(或者是 "mempool" )的抽象. 一個內存池真實地只是一類緩存, 它盡力一直保持一個空閑內存列表給緊急時使用.
一個內存池有一個類型 mempool_t ( 在 <linux/mempool.h> 中定義); 你可以使用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. pool_data是內存源。內存池對象創建完成后會自動調用alloc方法從pool_data上分配min_nr個元素用來填充內存池。
如果需要, 你可編寫特殊用途的函數來處理 mempool 的內存分配. 但是, 你常常只需要使內核 slab 分配器為你處理這個任務.
有 2 個函數 ( mempool_alloc_slab 和mempool_free_slab) 來進行內存分配.
因此, 設置內存池的代碼常常看來如此:
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 的使用應當少.
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/mempool.h>
#include <linux/gfp.h>
#include <linux/delay.h>
struct my_mempoolobj{
int val;
};
#define MY_MIN_MEMPOOL 20
struct kmem_cache * mempool_cachep = NULL;
mempool_t * my_pool = NULL;
struct my_mempoolobj * mempool_object = NULL;
static int __init mempool_init(void){
mempool_cachep = kmem_cache_create("mempool_cachep", sizeof(struct my_mempool), 0, 0, NULL);
if(!mempool_cachep)
return -ENOMEM;
my_pool = mempool_create(MY_MIN_MEMPOOL, mempool_alloc_slab, mempool_free_slab, mempool_cachep);
if(!my_pool)
return -ENOMEM;
mempool_object = (struct my_mempoolobj *)mempool_alloc(my_pool, GFP_KERNEL);
if(mempool_object)
printk("one object has been allocated!\n");
else
goto fail;
printk("memory pool curr_nr is %d, min_nr is %d\n", my_pool->curr_nr, my_pool->min_nr);
mempool_free(mempool_object, my_pool);
return 0;
fail:
mempool_destroy(my_pool);
kmem_cache_destroy(mempool_cachep);
return -1;
}
static void __exit mempool_exit(void){
printk("memory pool test module exit!\n");
if(my_pool){
mempool_destroy(my_pool);
printk("mempool has been destroy!\n");
}
if(mempool_cachep){
kmem_cache_destroy(mempool_cachep);
printk("cache has been destroy!\n");
}
}
module_init(mempool_init);
module_exit(mempool_exit);
