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