3. 物理内存管理:伙伴系统、slab分配器、连续内存分配器(CMA)
物理内存是系统最核心的硬件资源之一。Linux 内核通过三层递进的机制管理物理内存:伙伴系统(Buddy System)负责大块连续页面的分配与回收;slab 分配器在伙伴系统之上提供细粒度的小对象缓存;连续内存分配器(CMA)则为需要大量连续物理内存的设备(如 DMA、GPU)预留可动态迁移的区域。理解这三层结构,是掌握 mmap 与 DMA 缓冲区分配的基础。
3.1 伙伴系统(Buddy System)
伙伴系统是内核物理内存分配的核心算法,以 2order 个连续物理页(order 从 0 到 MAX_ORDER-1)为基本单位进行管理。其核心思想是将空闲页块按大小分组,分配时从最小满足需求的块中拆分,释放时与相邻的“伙伴”合并,从而减少外部碎片。
关键数据结构:每个内存域(zone)维护一个
free_area[MAX_ORDER] 数组,每个数组项对应一个特定 order 的空闲页块链表。
3.1.1 分配与释放流程
- 分配请求:调用
alloc_pages(gfp_mask, order),从目标 zone 的 free_area[order] 链表查找空闲块。 - 拆分(Buddy Splitting):若当前 order 链表为空,则向更高 order 链表请求一个块,将其一分为二,低半部分放入当前 order 链表,高半部分返回给调用者。
- 释放与合并(Buddy Coalescing):释放时检查伙伴页块是否空闲,若空闲则合并为更高 order 的块,并递归向上合并。
性能要点:伙伴系统分配/释放的时间复杂度为 O(1),但拆分和合并操作会引入少量开销。对于频繁分配/释放小对象的场景,应使用 slab 分配器。
3.1.2 伙伴系统结构图
3.2 slab 分配器
slab 分配器用于管理内核中频繁分配/释放的小对象(如 task_struct、inode、dentry 等)。它基于伙伴系统分配的连续页面,通过 slab 缓存 机制实现对象复用,避免频繁的伙伴系统操作。
3.2.1 核心概念
| 术语 | 说明 |
|---|---|
| cache | 管理特定类型对象的缓存,每个 cache 包含多个 slab。 |
| slab | 由一个或多个连续物理页组成,内部划分为多个等大小的对象槽。 |
| object | slab 中分配的最小单元,即内核数据结构实例。 |
| 着色(coloring) | 通过偏移调整对象在 slab 中的起始位置,优化 CPU 缓存利用率。 |
3.2.2 分配器层次结构
3.2.3 常用 API
/* 创建缓存 */
struct kmem_cache *kmem_cache_create(const char *name,
unsigned int size,
unsigned int align,
slab_flags_t flags,
void (*ctor)(void *));
/* 从缓存分配对象 */
void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags);
/* 释放对象回缓存 */
void kmem_cache_free(struct kmem_cache *cachep, void *objp);
/* 销毁缓存 */
void kmem_cache_destroy(struct kmem_cache *cachep);
注意事项:slab 分配器适用于大小固定、频繁分配/释放的对象。对于大小可变或极少分配的对象,应直接使用伙伴系统或
kmalloc(基于 slab 实现)。
3.3 连续内存分配器(CMA)
CMA 是为解决嵌入式系统中大块连续物理内存分配难题而设计的机制。它预留一块物理内存区域,平时可供可移动页面使用,当 DMA 等设备需要连续内存时,通过 页面迁移 将预留区域中的页面腾空,分配给设备。
3.3.1 工作原理
- 预留阶段:系统启动时通过设备树或内核参数预留一块物理内存区域(如
cma=64M)。 - 日常使用:预留区域中的页面被加入伙伴系统的 MIGRATE_CMA 类型链表,可被可移动页面(如匿名页、文件页)占用。
- 分配触发:设备驱动调用
dma_alloc_coherent或cma_alloc时,内核开始迁移预留区域中的页面。 - 页面迁移:通过
migrate_pages将占用页面迁移到其他区域,腾出连续物理空间。 - 分配完成:将腾空的连续物理页分配给设备,并返回物理地址和虚拟地址。
3.3.2 CMA 与伙伴系统、slab 的关系
| 特性 | 伙伴系统 | slab 分配器 | CMA |
|---|---|---|---|
| 分配粒度 | 2order 页(4KB ~ 4MB+) | 字节级(对象大小) | 连续页块(通常 1MB ~ 数百 MB) |
| 主要用途 | 通用物理页分配 | 内核小对象缓存 | DMA/GPU/多媒体缓冲区 |
| 碎片处理 | 合并伙伴减少外部碎片 | 对象复用减少内部碎片 | 页面迁移消除外部碎片 |
| 分配延迟 | 低(O(1)) | 极低(缓存命中) | 高(涉及页面迁移) |
| 底层依赖 | 硬件页面表 | 伙伴系统 | 伙伴系统 + 页面迁移 |
3.3.3 CMA 分配流程
3.3.4 驱动中使用 CMA
#include <linux/dma-mapping.h>
/* 分配 DMA 缓冲区(自动使用 CMA) */
dma_addr_t dma_handle;
void *cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);
/* 使用缓冲区... */
/* 释放 DMA 缓冲区 */
dma_free_coherent(dev, size, cpu_addr, dma_handle);
最佳实践:在嵌入式系统中,建议为多媒体、GPU、网卡等需要大块连续内存的设备预留 CMA 区域。CMA 区域大小通常为总内存的 5%~20%,具体取决于设备需求。
3.4 三层协同工作
伙伴系统、slab 分配器和 CMA 并非独立运行,而是形成完整的物理内存管理栈:
- 伙伴系统 是基石,管理所有物理页的分配与回收。
- slab 分配器 构建在伙伴系统之上,通过缓存机制高效管理小对象。
- CMA 利用伙伴系统的 MIGRATE_CMA 类型链表和页面迁移机制,实现大块连续内存的按需分配。