3. 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
};
struct cma 关键字段解析
字段 类型 说明 工程意义
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 区域,便于调试多区域场景下的内存压力。
struct cma 内存布局与字段关系 struct cma 实例 base_pfn | count | bitmap | order_per_bit | lock | name base_pfn 物理起始页帧 count 总页数 bitmap → 位图内存 order_per_bit 粒度阶数 bitmap 指向的位图内存 每个 bit 代表 2^order_per_bit 个连续页 位图总大小 = count / (2^order_per_bit) / 8 字节 锁保护机制 struct mutex lock 保护所有位图操作(alloc/free)

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_bitmap 与 struct cma 的位图字段对比
对比维度 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)。
struct cma_bitmap 模块化设计 struct cma_bitmap bitmap | count | pfn | lock bitmap 位图数据指针 count 位图总位数 pfn 区域起始 PFN lock 互斥锁 模块化优势:位图操作接口统一,便于替换底层实现(如支持稀疏位图或硬件加速) cma_bitmap_alloc(bitmap, order) / cma_bitmap_free(bitmap, pfn, order)

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 区域注册流程

  1. 解析预留内存节点:内核启动时,从设备树 /reserved-memory 节点或内核参数 cma= 中读取 CMA 区域配置。
  2. 调用 cma_init_reserved_mem():为每个 CMA 区域分配 struct cma 实例,初始化 base_pfn、count、order_per_bit。
  3. 分配位图内存:通过 bitmap_alloc() 分配位图空间,并挂接到 cma->bitmap。
  4. 添加到全局数组:将初始化完成的 struct cma 指针存入 cma_areas,cma_area_count 递增。
  5. 激活区域:调用 cma_activate_area() 将物理页加入伙伴系统,标记为 MIGRATE_CMA 类型。
CMA 区域管理关键函数
函数 作用 调用时机
cma_init_reserved_mem() 初始化单个 CMA 区域,设置 base_pfn、count、位图 内核启动早期
cma_activate_area() 将 CMA 区域物理页加入伙伴系统,设置迁移类型 内存管理子系统初始化时
cma_alloc() 从指定 CMA 区域分配连续页框 驱动运行时
cma_release() 释放之前分配的连续页框 驱动释放内存时
cma_get_area() 根据设备或索引获取 CMA 区域指针 驱动初始化时
CMA 区域管理架构 cma_areas[] 全局数组 cma_area_count = N cma_areas[0] 区域A: 0x4000_0000 size=32MB, order=0 cma_areas[1] 区域B: 0x5000_0000 size=64MB, order=1 cma_areas[2] 区域C: 0x6000_0000 size=128MB, order=0 bitmap: 8192 bits bitmap: 16384 bits bitmap: 32768 bits 驱动通过 cma_alloc(cma, count, align) 分配连续内存 cma = cma_get_area(dev, 0) 获取指定区域 每个 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),避免相互干扰。