深入理解 Linux CMA:连续内存分配器与大页池化机制深度实战
你的关注是我创作的最大动力!
关键词:Linux内核, CMA, 连续内存分配, HugeTLB, hugetlbfs, 大页池, DMA, 内存碎片, dma_alloc_coherent, 内存管理
描述:深度解析Linux内核CMA(Contiguous Memory Allocator)连续内存分配器的设计原理、算法实现与生产级调优。涵盖CMA区域初始化、迁移类型机制、设备树绑定、HugeTLB池化架构、hugetlbfs文件系统、透明大页THP与显式大页对比、内存碎片整理与CMA协同策略,以及GPU/DMA/多媒体等实际设备驱动中的CMA集成案例。
一、为什么需要连续内存:DMA 的物理约束
现代 SoC 上,大量外设(GPU 显示控制器、视频编解码器、DMA 控制器、Camera ISP、PCIe 端点设备)都需要物理上连续的内存块来进行 DMA 传输。原因很直接:
- 硬件限制:传统 DMA 控制器只有一个基地址寄存器 长度寄存器,无法做 scatter-gather
- 性能考量:连续内存避免 IOMMU 映射开销、TLB 抖动和多次传输调度
- 带宽需求:视频帧缓冲、GPU 纹理动辄需要数百 KB 至数十 MB 的连续区域
在 x86 架构上,由于 IOMMU(VT-d)普及,这个问题相对容易解决——通过 IOMMU 可以把多个物理不连续的页面映射为一个连续的IOVA。但在嵌入式 ARM/ARM64 领域,大量低成本 SoC 没有 IOMMU 或 IOMMU 能力有限,此时物理连续内存就是刚需。
Linux 内核启动早期通过 memblock 预留专用内存给特殊设备,但这种静态预留方式笨拙且利用率低。CMA 的出现改变了这一切。
二、CMA 架构总览
2.1 核心思想
CMA(Contiguous Memory Allocator,连续内存分配器)的核心设计非常巧妙:
预留但不锁定——在系统内存中划出一块区域标记为 CMA,但允许普通可移动页面(movable pages)在其上分配。当设备驱动需要连续内存时,通过页面迁移(page migration)把已占用的可移动页面搬走,腾出连续空间。
这本质上是一种"先礼后兵"策略:
- 平时 CMA 区域可以被普通分配器用作可移动页面
- 设备需要时,通过迁移回收这些页面
- 不需要时归还给 buddy allocator
2.2 内核配置
CONFIG_CMA=y
CONFIG_CMA_DEBUG=y
CONFIG_CMA_DEBUGFS=y
CMA_SIZE_MBYTES=256
CMA 区域大小可通过以下方式指定:
- 内核命令行参数:
cma=256M或cma=256M@0x10000000-0x20000000 - 设备树(Device Tree):
linux,cma节点 - 内核配置
CMA_SIZE_MBYTES
2.3 CMA 初始化流程
start_kernel()
└── setup_arch()
└── arm64_memblock_init() / x86_64 equivalent
└── memblock_alloc() /* 早期预留 */
└── mm_init()
└── cma_init()
└── cma_declare_contiguous() /* CMA 区域声明 */
└── memblock_alloc_range() /* memblock 级预留 */
└── ...
└── device_initcall()
└── cma_init_reserved_areas() /* 释放到 buddy */
关键两步:
- 早期预留:在 memblock 阶段保留物理区域,防止被 buddy allocator 吃掉
- 延迟释放:在 CMA 子系统就绪后,将预留区域以
MIGRATE_CMA迁移类型释放给 buddy allocator
2.4 MIGRATE_CMA 迁移类型
CMA 的核心机制建立在页面的迁移类型(migration type)之上。Linux 的 buddy allocator 将空闲页面按 MIGRATE_TYPES 分链表管理:
enum migratetype {
MIGRATE_UNMOVABLE, /* 内核代码、页表等 */
MIGRATE_MOVABLE, /* 用户页面、缓存等 */
MIGRATE_RECLAIMABLE, /* 可回收的内核页面 */
MIGRATE_PCPTYPES, /* Per-CPU 页面缓存 */
MIGRATE_HIGHATOMIC, /* 高原子分配 */
MIGRATE_ISOLATE, /* 不可分配 */
MIGRATE_TYPES,
};
对于 CMA 区域,使用 MIGRATE_CMA——这实际上是一个特殊标记的 MIGRATE_MOVABLE。当 CMA 分配请求到来时,分配器优先在 MIGRATE_CMA 链表中寻找,如果找不到,就迁移已分配的 MIGRATE_MOVABLE 页面到其他区域。
struct cma {
unsigned long base_pfn;
unsigned long count;
unsigned long *bitmap;
unsigned int order_per_bit; /* 一个 bit 表示 order 阶页面 */
struct cma *next; /* 多个 CMA 区域链表 */
const char *name;
#define CMA_FLAGS_DEV (1

发表评论 取消回复