Linux 內核內存分配 API 深度解析
內核開發中正確選擇內存分配函數至關重要,直接影響系統穩定性、效能與 DMA 能力。本文系統對比 kmalloc、vmalloc、alloc_pages 等核心 API 的原理、適用場景與避坑指南。
三大分配函數核心對比
| 維度 | kmalloc | vmalloc | alloc_pages / __get_free_pages |
| 物理連續性 | 保證物理連續 | 僅虛擬連續,物理可能離散 | 保證物理連續 (頁級) |
| 分配粒度 | 字節級 (最小 32/64B) | 頁級 (4KB 整數倍) | 頁級 (2^n 頁) |
| 最大分配 | 通常 ≤ 128KB (架構相關) | 理論可達 VMALLOC 空間上限 | 受限於階數 (通常 ≤ 4MB) |
| 映射修改 | 無需修改頁表 | 需更新內核頁表 | 建立直接映射 |
| 適用上下文 | 進程上下文 (GFP_KERNEL)、中斷 (GFP_ATOMIC) | 僅進程上下文 (會睡眠) | 進程上下文 |
| DMA 支持 | 直接支持 (配合 GFP_DMA) | 不支持 (需 bounce buffer) | 直接支持 |
| 釋放函數 | kfree() | vfree() | __free_pages() / free_pages() |
kmalloc 詳細解析
函數原型與關鍵標志
#include
void *kmalloc(size_t size, gfp_t flags);
常用 GFP 標志
| 標志 | 語義 | 適用場景 |
GFP_KERNEL | 可睡眠、可觸發回收、可進行文件系統/IO 操作 | 進程上下文、驅動初始化、文件操作 |
GFP_ATOMIC | 不可睡眠、不可觸發回收、高優先級、使用緊急預留內存 | 中斷處理、軟中斷、Tasklet、Spinlock 持有期間 |
GFP_DMA | 從 DMA 區分配 (x86: 低 16MB) | ISA 設備 DMA 緩衝區 |
GFP_HIGHUSER | 從高端內存分配用戶頁面 | 用戶空間映射 |
__GFP_NOWARN | 分配失敗不打印警告 | 嘗試性分配、有備選方案時 |
__GFP_NORETRY | 失敗立即返回,不重試 | 實時性敏感路徑 |
kmalloc 實現原理:SLAB/SLUB 分配器
- 基於對象緩存池:預先劃分固定大小的緩存 (kmalloc-32, kmalloc-64, ..., kmalloc-8192)
- 請求大小向上對齊到最近的緩存大小
- 具備著色、對齊、NODE 本地化 等特性
- 分配/釋放極快 (通常 < 100 納秒),無鎖路徑優化
vmalloc 詳細解析
核心特性
- 映射非物理連續的頁面到虛擬連續的內核地址空間 (
VMALLOC_START ~ VMALLOC_END)
- 需修改內核頁表,涉及
flush_tlb_kernel_range,開銷較大
- 適用於大塊虛擬連續內存:模塊加載、大數據結構、交換緩衝區
- 不能用於 DMA、不能在中斷上下文、
vmap 區域不可交換
vmap 區域管理
struct vm_struct *area = get_vm_area(size, VM_ALLOC);
if (!area) return NULL;
// 分配頁面並映射到 area->addr
// 失敗時需逐頁釋放並 vunmap
alloc_pages / __get_free_pages:頁級分配
// 分配 2^order 頁
struct page *alloc_pages(gfp_t gfp_mask, unsigned int order);
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order); // 返回邏輯地址
- 繞過 SLAB,直接從頁分配器獲取
- 適用於大塊物理連續內存 (如網卡環形緩衝區、GPU 緩衝區)
- 高階分配 (order > 3) 易失敗,建議配合
__GFP_RETRY_MAYFAIL
實戰選型決策樹
- 需要 DMA 或 物理連續 且 < 128KB →
kmalloc(GFP_KERNEL | GFP_DMA)
- 需要 大塊物理連續 (> 128KB) →
alloc_pages 或 CMA (Contiguous Memory Allocator)
- 只需 虛擬連續、大塊內存、非 DMA →
vmalloc
- 中斷上下文、持有 Spinlock → 只能
kmalloc(GFP_ATOMIC) 或 kmem_cache_alloc(..., GFP_ATOMIC)
- 頻繁分配/釋放固定大小結構體 →
kmem_cache_create + kmem_cache_alloc/free (最優效能)
常見錯誤與避坑
- ❌ 在中斷中用
GFP_KERNEL → 死鎖/系統崩潰
- ❌
vmalloc 內存用於 DMA → 數據損壞
- ❌ 忘記
kfree/vfree 配對 → 內存洩漏
- ❌ 假設
kmalloc 返回內存已清零 → 需手動 memset(ptr, 0, size) 或用 kzalloc
- ❌ 大量小塊用
vmalloc → 頁表壓力大、碎片化