8.1. kmalloc 的真實(shí)故事

2018-02-24 15:49 更新

8.1.?kmalloc 的真實(shí)故事

kmalloc 分配引擎是一個(gè)有力的工具并且容易學(xué)習(xí)因?yàn)樗鼘?duì) malloc 的相似性. 這個(gè)函數(shù)快(除非它阻塞)并且不清零它獲得的內(nèi)存; 分配的區(qū)仍然持有它原來的內(nèi)容.[28]?分配的區(qū)也是在物理內(nèi)存中連續(xù). 在下面幾節(jié), 我們?cè)敿?xì)討論 kmalloc, 因此你能比較它和我們后來要討論的內(nèi)存分配技術(shù).

8.1.1.?flags 參數(shù)

記住 kmalloc 原型是:

#include <linux/slab.h> 
void *kmalloc(size_t size, int flags); 

給 kmalloc 的第一個(gè)參數(shù)是要分配的塊的大小. 第 2 個(gè)參數(shù), 分配標(biāo)志, 非常有趣, 因?yàn)樗詭讉€(gè)方式控制 kmalloc 的行為.

最一般使用的標(biāo)志, GFP_KERNEL, 意思是這個(gè)分配((內(nèi)部最終通過調(diào)用 __get_freepages 來進(jìn)行, 它是 GFP 前綴的來源) 代表運(yùn)行在內(nèi)核空間的進(jìn)程而進(jìn)行的. 換句話說, 這意味著調(diào)用函數(shù)是代表一個(gè)進(jìn)程在執(zhí)行一個(gè)系統(tǒng)調(diào)用. 使用 GFP_KENRL 意味著 kmalloc 能夠使當(dāng)前進(jìn)程在少內(nèi)存的情況下睡眠來等待一頁(yè). 一個(gè)使用 GFP_KERNEL 來分配內(nèi)存的函數(shù)必須, 因此, 是可重入的并且不能在原子上下文中運(yùn)行. 當(dāng)當(dāng)前進(jìn)程睡眠, 內(nèi)核采取正確的動(dòng)作來定位一些空閑內(nèi)存, 或者通過刷新緩存到磁盤或者交換出去一個(gè)用戶進(jìn)程的內(nèi)存.

GFP_KERNEL 不一直是使用的正確分配標(biāo)志; 有時(shí) kmalloc 從一個(gè)進(jìn)程的上下文的外部調(diào)用. 例如, 這類的調(diào)用可能發(fā)生在中斷處理, tasklet, 和內(nèi)核定時(shí)器中. 在這個(gè)情況下, 當(dāng)前進(jìn)程不應(yīng)當(dāng)被置為睡眠, 并且驅(qū)動(dòng)應(yīng)當(dāng)使用一個(gè) GFP_ATOMIC 標(biāo)志來代替. 內(nèi)核正常地試圖保持一些空閑頁(yè)以便來滿足原子的分配. 當(dāng)使用 GFP_ATOMIC 時(shí), kmalloc 能夠使用甚至最后一個(gè)空閑頁(yè). 如果這最后一個(gè)空閑頁(yè)不存在, 但是, 分配失敗.

其他用來代替或者增添 GFP_KERNEL 和 GFP_ATOMIC 的標(biāo)志, 盡管它們 2 個(gè)涵蓋大部分設(shè)備驅(qū)動(dòng)的需要. 所有的標(biāo)志定義在 , 并且每個(gè)標(biāo)志用一個(gè)雙下劃線做前綴, 例如 __GFP_DMA. 另外, 有符號(hào)代表常常使用的標(biāo)志組合; 這些缺乏前綴并且有時(shí)被稱為分配優(yōu)先級(jí). 后者包括:

GFP_ATOMIC

用來從中斷處理和進(jìn)程上下文之外的其他代碼中分配內(nèi)存. 從不睡眠.

GFP_KERNEL

內(nèi)核內(nèi)存的正常分配. 可能睡眠.

GFP_USER

用來為用戶空間頁(yè)來分配內(nèi)存; 它可能睡眠.

GFP_HIGHUSER

如同 GFP_USER, 但是從高端內(nèi)存分配, 如果有. 高端內(nèi)存在下一個(gè)子節(jié)描述.

GFP_NOIO

GFP_NOFS

這個(gè)標(biāo)志功能如同 GFP_KERNEL, 但是它們?cè)黾酉拗频絻?nèi)核能做的來滿足請(qǐng)求. 一個(gè) GFP_NOFS 分配不允許進(jìn)行任何文件系統(tǒng)調(diào)用, 而 GFP_NOIO 根本不允許任何 I/O 初始化. 它們主要地用在文件系統(tǒng)和虛擬內(nèi)存代碼, 那里允許一個(gè)分配睡眠, 但是遞歸的文件系統(tǒng)調(diào)用會(huì)是一個(gè)壞注意.

上面列出的這些分配標(biāo)志可以是下列標(biāo)志的相或來作為參數(shù), 這些標(biāo)志改變這些分配如何進(jìn)行:

__GFP_DMA

這個(gè)標(biāo)志要求分配在能夠 DMA 的內(nèi)存區(qū). 確切的含義是平臺(tái)依賴的并且在下面章節(jié)來解釋.

__GFP_HIGHMEM

這個(gè)標(biāo)志指示分配的內(nèi)存可以位于高端內(nèi)存.

__GFP_COLD

正常地, 內(nèi)存分配器盡力返回"緩沖熱"的頁(yè) -- 可能在處理器緩沖中找到的頁(yè). 相反, 這個(gè)標(biāo)志請(qǐng)求一個(gè)"冷"頁(yè), 它在一段時(shí)間沒被使用. 它對(duì)分配頁(yè)作 DMA 讀是有用的, 此時(shí)在處理器緩沖中出現(xiàn)是無用的. 一個(gè)完整的對(duì)如何分配 DMA 緩存的討論看"直接內(nèi)存存取"一節(jié)在第 1 章.

__GFP_NOWARN

這個(gè)很少用到的標(biāo)志阻止內(nèi)核來發(fā)出警告(使用 printk ), 當(dāng)一個(gè)分配無法滿足.

__GFP_HIGH

這個(gè)標(biāo)志標(biāo)識(shí)了一個(gè)高優(yōu)先級(jí)請(qǐng)求, 它被允許來消耗甚至被內(nèi)核保留給緊急狀況的最后的內(nèi)存頁(yè).

__GFP_REPEAT

__GFP_NOFAIL

__GFP_NORETRY

這些標(biāo)志修改分配器如何動(dòng)作, 當(dāng)它有困難滿足一個(gè)分配. GFP_REPEAT 意思是" 更盡力些嘗試" 通過重復(fù)嘗試 -- 但是分配可能仍然失敗. __GFP_NOFAIL 標(biāo)志告訴分配器不要失敗; 它盡最大努力來滿足要求. 使用 GFP_NOFAIL 是強(qiáng)烈不推薦的; 可能從不會(huì)有有效的理由在一個(gè)設(shè)備驅(qū)動(dòng)中使用它. 最后, __GFP_NORETRY 告知分配器立即放棄如果得不到請(qǐng)求的內(nèi)存.

8.1.1.1.?內(nèi)存區(qū)

__GFP_DMA 和 __GFP_HIGHMEM 都有一個(gè)平臺(tái)相關(guān)的角色, 盡管對(duì)所有平臺(tái)它們的使用都有效.

Linux 內(nèi)核知道最少 3 個(gè)內(nèi)存區(qū): DMA-能夠 內(nèi)存, 普通內(nèi)存, 和高端內(nèi)存. 盡管通常地分配都發(fā)生于普通區(qū), 設(shè)置這些剛剛提及的位的任一個(gè)請(qǐng)求從不同的區(qū)來分配內(nèi)存. 這個(gè)想法是, 每個(gè)必須知道特殊內(nèi)存范圍(不是認(rèn)為所有的 RAM 等同)的計(jì)算機(jī)平臺(tái)將落入這個(gè)抽象中.

DMA-能夠 的內(nèi)存是位于一個(gè)優(yōu)先的地址范圍, 外設(shè)可以在這里進(jìn)行 DMA 存取. 在大部分的健全的平臺(tái), 所有的內(nèi)存都在這個(gè)區(qū). 在 x86, DMA 區(qū)用在 RAM 的前 16 MB, 這里傳統(tǒng)的 ISA 設(shè)備可以進(jìn)行 DMA; PCI 設(shè)備沒有這個(gè)限制.

高端內(nèi)存是一個(gè)機(jī)制用來允許在 32-位 平臺(tái)存取(相對(duì)地)大量?jī)?nèi)存. 如果沒有首先設(shè)置一個(gè)特殊的映射這個(gè)內(nèi)存無法直接從內(nèi)核存取并且通常更難使用. 如果你的驅(qū)動(dòng)使用大量?jī)?nèi)存, 但是, 如果它能夠使用高端內(nèi)存它將在大系統(tǒng)中工作的更好. 高端內(nèi)存如何工作以及如何使用它的詳情見第 1 章的"高端和低端內(nèi)存"一節(jié).

無論何時(shí)分配一個(gè)新頁(yè)來滿足一個(gè)內(nèi)存分配請(qǐng)求, 內(nèi)核都建立一個(gè)能夠在搜索中使用的內(nèi)存區(qū)的列表. 如果 __GFP_DMA 指定了, 只有 DMA 區(qū)被搜索: 如果在低端沒有內(nèi)存可用, 分配失敗. 如果沒有特別的標(biāo)志存取, 普通和 DMA 內(nèi)存都被搜索; 如果 __GFP_HIGHMEM 設(shè)置了, 所有的 3 個(gè)區(qū)都用來搜索一個(gè)空閑的頁(yè). (注意, 但是, kmalloc 不能分配高端內(nèi)存.)

情況在非統(tǒng)一內(nèi)存存取(NUMA)系統(tǒng)上更加復(fù)雜. 作為一個(gè)通用的規(guī)則, 分配器試圖定位進(jìn)行分配的處理器的本地的內(nèi)存, 盡管有幾個(gè)方法來改變這個(gè)行為.

內(nèi)存區(qū)后面的機(jī)制在 mm/page_alloc.c 中實(shí)現(xiàn), 而內(nèi)存區(qū)的初始化在平臺(tái)特定的文件中, 常常在 arch 目錄樹的 mm/init.c. 我們將在第 15 章再次討論這些主題.

8.1.2.? size 參數(shù)

內(nèi)核管理系統(tǒng)的物理內(nèi)存, 這些物理內(nèi)存只是以頁(yè)大小的塊來使用. 結(jié)果是, kmalloc 看來非常不同于一個(gè)典型的用戶空間 malloc 實(shí)現(xiàn). 一個(gè)簡(jiǎn)單的, 面向堆的分配技術(shù)可能很快有麻煩; 它可能在解決頁(yè)邊界時(shí)有困難. 因而, 內(nèi)核使用一個(gè)特殊的面向頁(yè)的分配技術(shù)來最好地利用系統(tǒng) RAM.

Linux 處理內(nèi)存分配通過創(chuàng)建一套固定大小的內(nèi)存對(duì)象池. 分配請(qǐng)求被這樣來處理, 進(jìn)入一個(gè)持有足夠大的對(duì)象的池子并且將整個(gè)內(nèi)存塊遞交給請(qǐng)求者. 內(nèi)存管理方案是非常復(fù)雜, 并且細(xì)節(jié)通常不是全部設(shè)備驅(qū)動(dòng)編寫者都感興趣的.

然而, 驅(qū)動(dòng)開發(fā)者應(yīng)當(dāng)記住的一件事情是, 內(nèi)核只能分配某些預(yù)定義的, 固定大小的字節(jié)數(shù)組. 如果你請(qǐng)求一個(gè)任意數(shù)量?jī)?nèi)存, 你可能得到稍微多于你請(qǐng)求的, 至多是 2 倍數(shù)量. 同樣, 程序員應(yīng)當(dāng)記住 kmalloc 能夠處理的最小分配是 32 或者 64 字節(jié), 依賴系統(tǒng)的體系所使用的頁(yè)大小.

kmalloc 能夠分配的內(nèi)存塊的大小有一個(gè)上限. 這個(gè)限制隨著體系和內(nèi)核配置選項(xiàng)而變化. 如果你的代碼是要完全可移植, 它不能指望可以分配任何大于 128 KB. 如果你需要多于幾個(gè) KB, 但是, 有個(gè)比 kmalloc 更好的方法來獲得內(nèi)存, 我們?cè)诒菊潞竺婷枋?


[28]?在其他的之中, 這暗含著你應(yīng)當(dāng)明確地清零可能暴露給用戶空間或者寫入設(shè)備的內(nèi)存; 否則, 你可能冒險(xiǎn)將應(yīng)當(dāng)保密的信息透露出去.

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)