W3Cschool
恭喜您成為首批注冊用戶
獲得88經驗值獎勵
一個設備驅動常常以反復分配許多相同大小的對象而結束. 如果內核已經維護了一套相同大小對象的內存池, 為什么不增加一些特殊的內存池給這些高容量的對象? 實際上, 內核確實實現(xiàn)了一個設施來創(chuàng)建這類內存池, 它常常被稱為一個后備緩存. 設備驅動常常不展示這類的內存行為, 它們證明使用一個后備緩存是對的, 但是, 有例外; 在 Linux 2.6 中 USB 和 SCSI 驅動使用緩存.
Linux 內核的緩存管理者有時稱為" slab 分配器". 因此, 它的功能和類型在 <linux/slab.h> 中聲明. slab 分配器實現(xiàn)有一個 kmem_cache_t 類型的緩存; 使用一個對 kmem_cache_create 的調用來創(chuàng)建它們:
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 *, unsigned long flags));
這個函數創(chuàng)建一個新的可以駐留任意數目全部同樣大小的內存區(qū)的緩存對象, 大小由 size 參數指定. name 參數和這個緩存關聯(lián)并且作為一個在追蹤問題時有用的管理信息; 通常, 它被設置為被緩存的結構類型的名子. 這個緩存保留一個指向 name 的指針, 而不是拷貝它, 因此驅動應當傳遞一個指向在靜態(tài)存儲中的名子的指針(常常這個名子只是一個文字字串). 這個名子不能包含空格.
offset 是頁內的第一個對象的偏移; 它可被用來確保一個對被分配的對象的特殊對齊, 但是你最可能會使用 0 來請求缺省值. flags 控制如何進行分配并且是下列標志的一個位掩碼:
SLAB_NO_REAP
設置這個標志保護緩存在系統(tǒng)查找內存時被削減. 設置這個標志通常是個壞主意; 重要的是避免不必要地限制內存分配器的行動自由.
SLAB_HWCACHE_ALIGN
這個標志需要每個數據對象被對齊到一個緩存行; 實際對齊依賴主機平臺的緩存分布. 這個選項可以是一個好的選擇, 如果在 SMP 機器上你的緩存包含頻繁存取的項. 但是, 用來獲得緩存行對齊的填充可以浪費可觀的內存量.
SLAB_CACHE_DMA
這個標志要求每個數據對象在 DMA 內存區(qū)分配.
還有一套標志用來調試緩存分配; 詳情見 mm/slab.c. 但是, 常常地, 在用來開發(fā)的系統(tǒng)中, 這些標志通過一個內核配置選項被全局性地設置
函數的 constructor 和 destructor 參數是可選函數( 但是可能沒有 destructor, 如果沒有 constructor ); 前者可以用來初始化新分配的對象, 后者可以用來"清理"對象在它們的內存被作為一個整體釋放回給系統(tǒng)之前.
構造函數和析構函數會有用, 但是有幾個限制你必須記住. 一個構造函數在分配一系列對象的內存時被調用; 因為內存可能持有幾個對象, 構造函數可能被多次調用. 你不能假設構造函數作為分配一個對象的一個立即的結果而被調用. 同樣地, 析構函數可能在以后某個未知的時間中調用, 不是立刻在一個對象被釋放后. 析構函數和構造函數可能或不可能被允許睡眠, 根據它們是否被傳遞 SLAB_CTOR_ATOMIC 標志(這里 CTOR 是 constructor 的縮寫).
為方便, 一個程序員可以使用相同的函數給析構函數和構造函數; slab 分配器常常傳遞 SLAB_CTOR_CONSTRUCTOR 標志當被調用者是一個構造函數.
一旦一個對象的緩存被創(chuàng)建, 你可以通過調用 kmem_cache_alloc 從它分配對象.
void *kmem_cache_alloc(kmem_cache_t *cache, int flags);
這里, cache 參數是你之前已經創(chuàng)建的緩存; flags 是你會傳遞給 kmalloc 的相同, 并且被參考如果 kmem_cache_alloc 需要出去并分配更多內存.
為釋放一個對象, 使用 kmem_cache_free:
void kmem_cache_free(kmem_cache_t *cache, const void *obj);
當驅動代碼用完這個緩存, 典型地當模塊被卸載, 它應當如下釋放它的緩存:
int kmem_cache_destroy(kmem_cache_t *cache);
這個銷毀操作只在從這個緩存中分配的所有的對象都已返回給它時才成功. 因此, 一個模塊應當檢查從 kmem_cache_destroy 的返回值; 一個失敗指示某類在模塊中的內存泄漏(因為某些對象已被丟失.)
使用后備緩存的一方面益處是內核維護緩沖使用的統(tǒng)計. 這些統(tǒng)計可從 /proc/slabinfo 獲得.
是時候給個例子了. scullc 是一個簡化的 scull 模塊的版本, 它只實現(xiàn)空設備 -- 永久的內存區(qū). 不象 scull, 它使用 kmalloc, scullc 使用內存緩存. 量子的大小可在編譯時和加載時修改, 但是不是在運行時 -- 這可能需要創(chuàng)建一個新內存區(qū), 并且我們不想處理這些不必要的細節(jié).
scullc 使用一個完整的例子, 可用來試驗 slab 分配器. 它區(qū)別于 scull 只在幾行代碼. 首先, 我們必須聲明我們自己的 slab 緩存:
/* declare one cache pointer: use it for all devices */
kmem_cache_t *scullc_cache;
slab 緩存的創(chuàng)建以這樣的方式處理( 在模塊加載時 ):
/* scullc_init: create a cache for our quanta */
scullc_cache = kmem_cache_create("scullc", scullc_quantum,
0, SLAB_HWCACHE_ALIGN, NULL, NULL); /* no ctor/dtor */
if (!scullc_cache)
{
scullc_cleanup();
return -ENOMEM;
}
這是它如何分配內存量子:
/* Allocate a quantum using the memory cache */
if (!dptr->data[s_pos])
{
dptr->data[s_pos] = kmem_cache_alloc(scullc_cache, GFP_KERNEL);
if (!dptr->data[s_pos])
goto nomem;
memset(dptr->data[s_pos], 0, scullc_quantum);
}
還有這些代碼行釋放內存:
for (i = 0; i < qset; i++)
if (dptr->data[i])
kmem_cache_free(scullc_cache, dptr->data[i]);
最后, 在模塊卸載時, 我們不得不返回緩存給系統(tǒng):
/* scullc_cleanup: release the cache of our quanta */
if (scullc_cache)
kmem_cache_destroy(scullc_cache);
從 scull 到 scullc 的主要不同是稍稍的速度提升以及更好的內存使用. 因為量子從一個恰好是合適大小的內存片的池中分配, 它們在內存中的排列是盡可能的密集, 與 scull 量子的相反, 它帶來一個不可預測的內存碎片.
在內核中有不少地方內存分配不允許失敗. 作為一個在這些情況下確保分配的方式, 內核開發(fā)者創(chuàng)建了一個已知為內存池(或者是 "mempool" )的抽象. 一個內存池真實地只是一類后備緩存, 它盡力一直保持一個空閑內存列表給緊急時使用.
一個內存池有一個類型 mempool_t ( 在 <linux/mempool.h> 中定義); 你可以使用 mempool_create 創(chuàng)建一個:
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);
一旦已創(chuàng)建了內存池, 可以分配和釋放對象,使用:
void *mempool_alloc(mempool_t *pool, int gfp_mask);
void mempool_free(void *element, mempool_t *pool);
當內存池創(chuàng)建了, 分配函數將被調用足夠的次數來創(chuàng)建一個預先分配的對象池. 因此, 對 mempool_alloc 的調用試圖從分配函數請求額外的對象; 如果那個分配失敗, 一個預先分配的對象(如果有剩下的)被返回. 當一個對象被用 mempool_free 釋放, 它保留在池中, 如果對齊預分配的對象數目小于最小量; 否則, 它將被返回給系統(tǒng).
一個 mempool 可被重新定大小, 使用:
int mempool_resize(mempool_t *pool, int new_min_nr, int gfp_mask);
這個調用, 如果成功, 調整內存池的大小至少有 new_min_nr 個對象. 如果你不再需要一個內存池, 返回給系統(tǒng)使用:
void mempool_destroy(mempool_t *pool);
你編寫返回所有的分配的對象, 在銷毀 mempool 之前, 否則會產生一個內核 oops.
如果你考慮在你的驅動中使用一個 mempool, 請記住一件事: mempools 分配一塊內存在一個鏈表中, 對任何真實的使用是空閑和無用的. 容易使用 mempools 消耗大量的內存. 在幾乎每個情況下, 首選的可選項是不使用 mempool 并且代替以簡單處理分配失敗的可能性. 如果你的驅動有任何方法以不危害到系統(tǒng)完整性的方式來響應一個分配失敗, 就這樣做. 驅動代碼中的 mempools 的使用應當少.
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: