## 8.1.?kmalloc 的真實故事
kmalloc 分配引擎是一個有力的工具并且容易學習因為它對 malloc 的相似性. 這個函數快(除非它阻塞)并且不清零它獲得的內存; 分配的區仍然持有它原來的內容.[[28](http://oss.org.cn/kernel-book/ldd3/ch08.html#ftn.id450180)]?分配的區也是在物理內存中連續. 在下面幾節, 我們詳細討論 kmalloc, 因此你能比較它和我們后來要討論的內存分配技術.
### 8.1.1.?flags 參數
記住 kmalloc 原型是:
~~~
#include <linux/slab.h>
void *kmalloc(size_t size, int flags);
~~~
給 kmalloc 的第一個參數是要分配的塊的大小. 第 2 個參數, 分配標志, 非常有趣, 因為它以幾個方式控制 kmalloc 的行為.
最一般使用的標志, GFP_KERNEL, 意思是這個分配((內部最終通過調用 __get_free_pages 來進行, 它是 GFP_ 前綴的來源) 代表運行在內核空間的進程而進行的. 換句話說, 這意味著調用函數是代表一個進程在執行一個系統調用. 使用 GFP_KENRL 意味著 kmalloc 能夠使當前進程在少內存的情況下睡眠來等待一頁. 一個使用 GFP_KERNEL 來分配內存的函數必須, 因此, 是可重入的并且不能在原子上下文中運行. 當當前進程睡眠, 內核采取正確的動作來定位一些空閑內存, 或者通過刷新緩存到磁盤或者交換出去一個用戶進程的內存.
GFP_KERNEL 不一直是使用的正確分配標志; 有時 kmalloc 從一個進程的上下文的外部調用. 例如, 這類的調用可能發生在中斷處理, tasklet, 和內核定時器中. 在這個情況下, 當前進程不應當被置為睡眠, 并且驅動應當使用一個 GFP_ATOMIC 標志來代替. 內核正常地試圖保持一些空閑頁以便來滿足原子的分配. 當使用 GFP_ATOMIC 時, kmalloc 能夠使用甚至最后一個空閑頁. 如果這最后一個空閑頁不存在, 但是, 分配失敗.
其他用來代替或者增添 GFP_KERNEL 和 GFP_ATOMIC 的標志, 盡管它們 2 個涵蓋大部分設備驅動的需要. 所有的標志定義在 , 并且每個標志用一個雙下劃線做前綴, 例如 __GFP_DMA. 另外, 有符號代表常常使用的標志組合; 這些缺乏前綴并且有時被稱為分配優先級. 后者包括:
GFP_ATOMIC
用來從中斷處理和進程上下文之外的其他代碼中分配內存. 從不睡眠.
GFP_KERNEL
內核內存的正常分配. 可能睡眠.
GFP_USER
用來為用戶空間頁來分配內存; 它可能睡眠.
GFP_HIGHUSER
如同 GFP_USER, 但是從高端內存分配, 如果有. 高端內存在下一個子節描述.
GFP_NOIO
GFP_NOFS
這個標志功能如同 GFP_KERNEL, 但是它們增加限制到內核能做的來滿足請求. 一個 GFP_NOFS 分配不允許進行任何文件系統調用, 而 GFP_NOIO 根本不允許任何 I/O 初始化. 它們主要地用在文件系統和虛擬內存代碼, 那里允許一個分配睡眠, 但是遞歸的文件系統調用會是一個壞注意.
上面列出的這些分配標志可以是下列標志的相或來作為參數, 這些標志改變這些分配如何進行:
__GFP_DMA
這個標志要求分配在能夠 DMA 的內存區. 確切的含義是平臺依賴的并且在下面章節來解釋.
__GFP_HIGHMEM
這個標志指示分配的內存可以位于高端內存.
__GFP_COLD
正常地, 內存分配器盡力返回"緩沖熱"的頁 -- 可能在處理器緩沖中找到的頁. 相反, 這個標志請求一個"冷"頁, 它在一段時間沒被使用. 它對分配頁作 DMA 讀是有用的, 此時在處理器緩沖中出現是無用的. 一個完整的對如何分配 DMA 緩存的討論看"直接內存存取"一節在第 1 章.
__GFP_NOWARN
這個很少用到的標志阻止內核來發出警告(使用 printk ), 當一個分配無法滿足.
__GFP_HIGH
這個標志標識了一個高優先級請求, 它被允許來消耗甚至被內核保留給緊急狀況的最后的內存頁.
__GFP_REPEAT
__GFP_NOFAIL
__GFP_NORETRY
這些標志修改分配器如何動作, 當它有困難滿足一個分配. __GFP_REPEAT 意思是" 更盡力些嘗試" 通過重復嘗試 -- 但是分配可能仍然失敗. __GFP_NOFAIL 標志告訴分配器不要失敗; 它盡最大努力來滿足要求. 使用 __GFP_NOFAIL 是強烈不推薦的; 可能從不會有有效的理由在一個設備驅動中使用它. 最后, __GFP_NORETRY 告知分配器立即放棄如果得不到請求的內存.
#### 8.1.1.1.?內存區
__GFP_DMA 和 __GFP_HIGHMEM 都有一個平臺相關的角色, 盡管對所有平臺它們的使用都有效.
Linux 內核知道最少 3 個內存區: DMA-能夠 內存, 普通內存, 和高端內存. 盡管通常地分配都發生于普通區, 設置這些剛剛提及的位的任一個請求從不同的區來分配內存. 這個想法是, 每個必須知道特殊內存范圍(不是認為所有的 RAM 等同)的計算機平臺將落入這個抽象中.
DMA-能夠 的內存是位于一個優先的地址范圍, 外設可以在這里進行 DMA 存取. 在大部分的健全的平臺, 所有的內存都在這個區. 在 x86, DMA 區用在 RAM 的前 16 MB, 這里傳統的 ISA 設備可以進行 DMA; PCI 設備沒有這個限制.
高端內存是一個機制用來允許在 32-位 平臺存取(相對地)大量內存. 如果沒有首先設置一個特殊的映射這個內存無法直接從內核存取并且通常更難使用. 如果你的驅動使用大量內存, 但是, 如果它能夠使用高端內存它將在大系統中工作的更好. 高端內存如何工作以及如何使用它的詳情見第 1 章的"高端和低端內存"一節.
無論何時分配一個新頁來滿足一個內存分配請求, 內核都建立一個能夠在搜索中使用的內存區的列表. 如果 __GFP_DMA 指定了, 只有 DMA 區被搜索: 如果在低端沒有內存可用, 分配失敗. 如果沒有特別的標志存取, 普通和 DMA 內存都被搜索; 如果 __GFP_HIGHMEM 設置了, 所有的 3 個區都用來搜索一個空閑的頁. (注意, 但是, kmalloc 不能分配高端內存.)
情況在非統一內存存取(NUMA)系統上更加復雜. 作為一個通用的規則, 分配器試圖定位進行分配的處理器的本地的內存, 盡管有幾個方法來改變這個行為.
內存區后面的機制在 mm/page_alloc.c 中實現, 而內存區的初始化在平臺特定的文件中, 常常在 arch 目錄樹的 mm/init.c. 我們將在第 15 章再次討論這些主題.
### 8.1.2.? size 參數
內核管理系統的物理內存, 這些物理內存只是以頁大小的塊來使用. 結果是, kmalloc 看來非常不同于一個典型的用戶空間 malloc 實現. 一個簡單的, 面向堆的分配技術可能很快有麻煩; 它可能在解決頁邊界時有困難. 因而, 內核使用一個特殊的面向頁的分配技術來最好地利用系統 RAM.
Linux 處理內存分配通過創建一套固定大小的內存對象池. 分配請求被這樣來處理, 進入一個持有足夠大的對象的池子并且將整個內存塊遞交給請求者. 內存管理方案是非常復雜, 并且細節通常不是全部設備驅動編寫者都感興趣的.
然而, 驅動開發者應當記住的一件事情是, 內核只能分配某些預定義的, 固定大小的字節數組. 如果你請求一個任意數量內存, 你可能得到稍微多于你請求的, 至多是 2 倍數量. 同樣, 程序員應當記住 kmalloc 能夠處理的最小分配是 32 或者 64 字節, 依賴系統的體系所使用的頁大小.
kmalloc 能夠分配的內存塊的大小有一個上限. 這個限制隨著體系和內核配置選項而變化. 如果你的代碼是要完全可移植, 它不能指望可以分配任何大于 128 KB. 如果你需要多于幾個 KB, 但是, 有個比 kmalloc 更好的方法來獲得內存, 我們在本章后面描述.
* * *
[[28](http://oss.org.cn/kernel-book/ldd3/ch08.html#id450180)]?在其他的之中, 這暗含著你應當明確地清零可能暴露給用戶空間或者寫入設備的內存; 否則, 你可能冒險將應當保密的信息透露出去.
- Linux設備驅動第三版
- 第 1 章 設備驅動簡介
- 1.1. 驅動程序的角色
- 1.2. 劃分內核
- 1.3. 設備和模塊的分類
- 1.4. 安全問題
- 1.5. 版本編號
- 1.6. 版權條款
- 1.7. 加入內核開發社團
- 1.8. 本書的內容
- 第 2 章 建立和運行模塊
- 2.1. 設置你的測試系統
- 2.2. Hello World 模塊
- 2.3. 內核模塊相比于應用程序
- 2.4. 編譯和加載
- 2.5. 內核符號表
- 2.6. 預備知識
- 2.7. 初始化和關停
- 2.8. 模塊參數
- 2.9. 在用戶空間做
- 2.10. 快速參考
- 第 3 章 字符驅動
- 3.1. scull 的設計
- 3.2. 主次編號
- 3.3. 一些重要數據結構
- 3.4. 字符設備注冊
- 3.5. open 和 release
- 3.6. scull 的內存使用
- 3.7. 讀和寫
- 3.8. 使用新設備
- 3.9. 快速參考
- 第 4 章 調試技術
- 4.1. 內核中的調試支持
- 4.2. 用打印調試
- 4.3. 用查詢來調試
- 4.4. 使用觀察來調試
- 4.5. 調試系統故障
- 4.6. 調試器和相關工具
- 第 5 章 并發和競爭情況
- 5.1. scull 中的缺陷
- 5.2. 并發和它的管理
- 5.3. 旗標和互斥體
- 5.4. Completions 機制
- 5.5. 自旋鎖
- 5.6. 鎖陷阱
- 5.7. 加鎖的各種選擇
- 5.8. 快速參考
- 第 6 章 高級字符驅動操作
- 6.1. ioctl 接口
- 6.2. 阻塞 I/O
- 6.3. poll 和 select
- 6.4. 異步通知
- 6.5. 移位一個設備
- 6.6. 在一個設備文件上的存取控制
- 6.7. 快速參考
- 第 7 章 時間, 延時, 和延后工作
- 7.1. 測量時間流失
- 7.2. 獲知當前時間
- 7.3. 延后執行
- 7.4. 內核定時器
- 7.5. Tasklets 機制
- 7.6. 工作隊列
- 7.7. 快速參考
- 第 8 章 分配內存
- 8.1. kmalloc 的真實故事
- 8.2. 后備緩存
- 8.3. get_free_page 和其友
- 8.4. 每-CPU 的變量
- 8.5. 獲得大量緩沖
- 8.6. 快速參考
- 第 9 章 與硬件通訊
- 9.1. I/O 端口和 I/O 內存
- 9.2. 使用 I/O 端口
- 9.3. 一個 I/O 端口例子
- 9.4. 使用 I/O 內存
- 9.5. 快速參考
- 第 10 章 中斷處理
- 10.1. 準備并口
- 10.2. 安裝一個中斷處理
- 10.3. 前和后半部
- 10.4. 中斷共享
- 10.5. 中斷驅動 I/O
- 10.6. 快速參考
- 第 11 章 內核中的數據類型
- 11.1. 標準 C 類型的使用
- 11.2. 安排一個明確大小給數據項
- 11.3. 接口特定的類型
- 11.4. 其他移植性問題
- 11.5. 鏈表
- 11.6. 快速參考
- 第 12 章 PCI 驅動
- 12.1. PCI 接口
- 12.2. 回顧: ISA
- 12.3. PC/104 和 PC/104+
- 12.4. 其他的 PC 總線
- 12.5. SBus
- 12.6. NuBus 總線
- 12.7. 外部總線
- 12.8. 快速參考
- 第 13 章 USB 驅動
- 13.1. USB 設備基礎知識
- 13.2. USB 和 sysfs
- 13.3. USB 的 Urbs
- 13.4. 編寫一個 USB 驅動
- 13.5. 無 urb 的 USB 傳送
- 13.6. 快速參考
- 第 14 章 Linux 設備模型
- 14.1. Kobjects, Ksets 和 Subsystems
- 14.2. 低級 sysfs 操作
- 14.3. 熱插拔事件產生
- 14.4. 總線, 設備, 和驅動
- 14.5. 類
- 14.6. 集成起來
- 14.7. 熱插拔
- 14.8. 處理固件
- 14.9. 快速參考
- 第 15 章 內存映射和 DMA
- 15.1. Linux 中的內存管理
- 15.2. mmap 設備操作
- 15.3. 進行直接 I/O
- 15.4. 直接內存存取
- 15.5. 快速參考
- 第 16 章 塊驅動
- 16.1. 注冊
- 16.2. 塊設備操作
- 16.3. 請求處理
- 16.4. 一些其他的細節
- 16.5. 快速參考
- 第 17 章 網絡驅動
- 17.1. snull 是如何設計的
- 17.2. 連接到內核
- 17.3. net_device 結構的詳情
- 17.4. 打開與關閉
- 17.5. 報文傳送
- 17.6. 報文接收
- 17.7. 中斷處理
- 17.8. 接收中斷緩解
- 17.9. 連接狀態的改變
- 17.10. Socket 緩存
- 17.11. MAC 地址解析
- 17.12. 定制 ioctl 命令
- 17.13. 統計信息
- 17.14. 多播
- 17.15. 幾個其他細節
- 17.16. 快速參考
- 第 18 章 TTY 驅動
- 18.1. 一個小 TTY 驅動
- 18.2. tty_driver 函數指針
- 18.3. TTY 線路設置
- 18.4. ioctls 函數
- 18.5. TTY 設備的 proc 和 sysfs 處理
- 18.6. tty_driver 結構的細節
- 18.7. tty_operaions 結構的細節
- 18.8. tty_struct 結構的細節
- 18.9. 快速參考