1. Linux内核内存管理概述
在Linux内核开发中,内存管理是最基础也是最关键的子系统之一。与用户空间程序使用malloc/free不同,内核提供了更精细、更专业的内存分配机制。理解这些机制对于开发高效稳定的内核模块至关重要。
内核内存分配主要分为两大类别:
- 页级分配:基于伙伴系统(Buddy System),分配物理连续的内存页,适合大块内存需求
- Slab分配:基于Slab/Slub/Slob分配器,提供字节级的内存分配,特别适合小对象频繁分配释放的场景
这两种机制不是相互替代的关系,而是针对不同场景设计的互补方案。选择哪种分配方式,需要根据具体的使用场景、内存大小需求和性能要求来决定。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 页级分配机制详解
2.1 伙伴系统基础
伙伴系统是Linux内核管理物理内存的核心算法,它将所有空闲页框组织为11个链表,分别对应1,2,4,...,1024个连续页框的块。这种设计使得分配和释放大块连续物理内存变得高效。
关键数据结构:
c复制struct zone {
...
struct free_area free_area[MAX_ORDER];
...
};
struct free_area {
struct list_head free_list[MIGRATE_TYPES];
unsigned long nr_free;
};
2.2 页分配API实战
alloc_pages()
c复制struct page *alloc_pages(gfp_t gfp_mask, unsigned int order);
这是最基础的页分配函数,返回指向第一页的page结构指针。order参数指定要分配2^order个连续页。
典型使用场景:
c复制// 分配8个连续页(假设PAGE_SIZE=4K,即32KB)
struct page *page = alloc_pages(GFP_KERNEL, 3);
if (!page) {
// 处理分配失败
}
__get_free_pages()
c复制unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);
这个函数与alloc_pages()类似,但直接返回页面的虚拟地址(在内核线性映射区),使用更方便。
使用示例:
c复制// 分配4个连续页(16KB)
unsigned long addr = __get_free_pages(GFP_KERNEL, 2);
if (!addr) {
// 处理分配失败
}
// 可以直接使用返回的地址
memset((void *)addr, 0, PAGE_SIZE << 2);
释放页内存
对应的释放函数必须与分配函数匹配:
c复制void free_pages(unsigned long addr, unsigned int order);
void __free_pages(struct page *page, unsigned int order);
重要提示:释放的order必须与分配时一致,否则会导致严重的内存错误。
2.3 GFP掩码深度解析
gfp_mask不仅控制分配行为,还影响内核的内存回收策略。常见的组合方式:
-
基本类型:
GFP_KERNEL:标准内核分配,可能睡眠GFP_ATOMIC:原子分配,不会睡眠GFP_NOWAIT:不等待直接返回,介于KERNEL和ATOMIC之间
-
修饰标志:
__GFP_ZERO:分配后清零内存__GFP_HIGHMEM:允许从高端内存分配__GFP_NOFAIL:分配必须成功,慎用__GFP_RETRY_MAYFAIL:允许重试但可能失败
-
DMA相关:
