8.1. kmalloc 的真實故事

2018-02-24 15:49 更新

8.1.?kmalloc 的真實故事

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

8.1.1.?flags 參數(shù)

記住 kmalloc 原型是:

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

給 kmalloc 的第一個參數(shù)是要分配的塊的大小. 第 2 個參數(shù), 分配標志, 非常有趣, 因為它以幾個方式控制 kmalloc 的行為.

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

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

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

GFP_ATOMIC

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

GFP_KERNEL

內核內存的正常分配. 可能睡眠.

GFP_USER

用來為用戶空間頁來分配內存; 它可能睡眠.

GFP_HIGHUSER

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

GFP_NOIO

GFP_NOFS

這個標志功能如同 GFP_KERNEL, 但是它們增加限制到內核能做的來滿足請求. 一個 GFP_NOFS 分配不允許進行任何文件系統(tǒng)調用, 而 GFP_NOIO 根本不允許任何 I/O 初始化. 它們主要地用在文件系統(tǒng)和虛擬內存代碼, 那里允許一個分配睡眠, 但是遞歸的文件系統(tǒng)調用會是一個壞注意.

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

__GFP_DMA

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

__GFP_HIGHMEM

這個標志指示分配的內存可以位于高端內存.

__GFP_COLD

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

__GFP_NOWARN

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

__GFP_HIGH

這個標志標識了一個高優(yōu)先級請求, 它被允許來消耗甚至被內核保留給緊急狀況的最后的內存頁.

__GFP_REPEAT

__GFP_NOFAIL

__GFP_NORETRY

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

8.1.1.1.?內存區(qū)

__GFP_DMA 和 __GFP_HIGHMEM 都有一個平臺相關的角色, 盡管對所有平臺它們的使用都有效.

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

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

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

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

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

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

8.1.2.? size 參數(shù)

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

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

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

kmalloc 能夠分配的內存塊的大小有一個上限. 這個限制隨著體系和內核配置選項而變化. 如果你的代碼是要完全可移植, 它不能指望可以分配任何大于 128 KB. 如果你需要多于幾個 KB, 但是, 有個比 kmalloc 更好的方法來獲得內存, 我們在本章后面描述.


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

以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號