3、CMA核心数据结构:struct cma、struct cma_bitmap、cma区域管理结构体解析
CMA(Contiguous Memory Allocator)在内核中的实现依赖于一套精心设计的数据结构。理解这些结构是掌握CMA工作原理、进行驱动缓冲优化和内存压力调试的基础。本章将深入解析三个核心结构体:struct cma(区域描述符)、struct cma_bitmap(位图管理)以及它们共同构成的区域管理模型。
核心要点: CMA 的三大数据结构构成了“区域描述 → 空闲状态追踪 → 分配/释放操作”的完整链路。驱动开发者通过操作这些结构,可以实现对连续物理内存的高效管理与动态预留。
3.1 struct cma:CMA 区域描述符
struct cma 是每个 CMA 区域的“身份证”,它描述了该区域的物理基地址、总大小、位图信息以及锁保护机制。系统中每个 CMA 区域都对应一个 struct cma 实例,多个实例通过全局数组 cma_areas 管理。
// include/linux/cma.h
struct cma {
unsigned long base_pfn; // 区域起始物理页帧号 (PFN)
unsigned long count; // 区域包含的物理页总数
unsigned long *bitmap; // 指向位图内存的指针
unsigned int order_per_bit; // 每个位图bit代表的连续页框阶数 (2^order_per_bit)
struct mutex lock; // 保护位图并发访问的互斥锁
const char *name; // 区域名称(用于调试和 /proc 展示)
#ifdef CONFIG_CMA_DEBUG
unsigned long alloc_pages_failed; // 分配失败计数(调试用)
#endif
};
| 字段 | 类型 | 说明 | 工程意义 |
|---|---|---|---|
base_pfn |
unsigned long |
区域起始物理页帧号 | 驱动通过 PFN 转换为物理地址,用于 DMA 或外设映射 |
count |
unsigned long |
区域总页数 | 决定 CMA 区域容量,直接影响可分配的连续内存大小 |
bitmap |
unsigned long * |
位图指针,管理空闲页块 | 位图大小 = count / (2^order_per_bit) / BITS_PER_LONG |
order_per_bit |
unsigned int |
每个 bit 代表的页框阶数 | 典型值为 0(每页1bit),大区域可设为 1 或 2 以缩小位图 |
lock |
struct mutex |
互斥锁 | 防止多核并发分配/释放导致位图损坏 |
name |
const char * |
区域名称 | 用于 /proc/cma 调试输出,便于区分不同区域 |
驱动优化提示: 在编写驱动时,通过
cma_get_name(cma) 获取区域名称,结合 base_pfn 可以精确判断当前分配来自哪个 CMA 区域,便于调试多区域场景下的内存压力。
3.2 struct cma_bitmap:位图管理结构
在较新版本的内核(5.4+)中,CMA 引入了 struct cma_bitmap 来封装位图及其元数据,使位图操作更加模块化。该结构体将位图指针、位图大小和锁机制整合在一起,便于在不同 CMA 实现间复用。
// mm/cma.c (内核 5.4+)
struct cma_bitmap {
unsigned long *bitmap; // 位图数据
unsigned int count; // 位图总位数(= 区域页数 / 2^order_per_bit)
unsigned long pfn; // 区域起始 PFN(冗余,用于快速校验)
struct mutex lock; // 位图操作锁
};
| 对比维度 | struct cma(传统方式) | struct cma_bitmap(模块化方式) |
|---|---|---|
| 位图指针 | 直接 unsigned long *bitmap |
封装在 struct cma_bitmap 内 |
| 位图大小 | 通过 count >> order_per_bit 计算 |
显式存储 count 字段 |
| 锁 | struct mutex lock 在 cma 中 |
锁移至 bitmap 结构内部 |
| 可维护性 | 字段分散,修改需同步多处 | 高内聚,便于单元测试和替换 |
| 内核版本 | 所有版本 | 5.4+ 逐步引入 |
版本兼容性注意: 如果你的驱动需要兼容 4.19 及更早内核,请使用
struct cma 的直接位图字段;若基于 5.4+ 开发,建议优先使用 struct cma_bitmap 提供的封装接口(如 cma_bitmap_alloc、cma_bitmap_free)。
3.3 CMA 区域管理结构体解析
除了单个区域描述符和位图,CMA 子系统还通过全局管理结构体来协调多个 CMA 区域。核心是 cma_areas 数组和 cma_area_count 计数器。
// mm/cma.c
struct cma *cma_areas; // CMA 区域数组,每个元素是一个 struct cma
unsigned int cma_area_count; // 当前系统中 CMA 区域的数量
// 通过设备树或内核参数注册的 CMA 区域会被依次添加到该数组
// 驱动可通过 cma_get_area(dev, index) 获取指定区域
3.3.1 区域注册流程
- 解析预留内存节点:内核启动时,从设备树
/reserved-memory节点或内核参数cma=中读取 CMA 区域配置。 - 调用
cma_init_reserved_mem():为每个 CMA 区域分配struct cma实例,初始化base_pfn、count、order_per_bit。 - 分配位图内存:通过
bitmap_alloc()分配位图空间,并挂接到cma->bitmap。 - 添加到全局数组:将初始化完成的
struct cma指针存入cma_areas,cma_area_count递增。 - 激活区域:调用
cma_activate_area()将物理页加入伙伴系统,标记为 MIGRATE_CMA 类型。
| 函数 | 作用 | 调用时机 |
|---|---|---|
cma_init_reserved_mem() |
初始化单个 CMA 区域,设置 base_pfn、count、位图 | 内核启动早期 |
cma_activate_area() |
将 CMA 区域物理页加入伙伴系统,设置迁移类型 | 内存管理子系统初始化时 |
cma_alloc() |
从指定 CMA 区域分配连续页框 | 驱动运行时 |
cma_release() |
释放之前分配的连续页框 | 驱动释放内存时 |
cma_get_area() |
根据设备或索引获取 CMA 区域指针 | 驱动初始化时 |
3.4 数据结构关系总结
三个核心数据结构共同构成了 CMA 的内存管理模型:
struct cma:区域级描述符,包含物理地址、大小、位图指针和锁。是驱动与 CMA 交互的主要接口。struct cma_bitmap:位图级封装,将位图数据、元数据和锁整合,提供模块化操作接口(内核 5.4+)。cma_areas[]全局数组:系统级管理,维护所有 CMA 区域实例,支持多区域并行分配。
工程实践要点:
- 驱动开发时,通过
cma_get_area()获取区域指针,避免直接访问cma_areas全局数组。 - 在内存压力测试中,监控
cma->alloc_pages_failed(调试版本)可以快速定位分配失败的区域。 - 位图大小直接影响分配性能:
order_per_bit越大,位图越小,但分配粒度变粗,可能导致内部碎片。 - 多区域场景下,建议为不同用途的驱动分配独立的 CMA 区域(如 Camera、GPU、VPU),避免相互干扰。