CMA工程实践 驱动缓冲优化与内存压力测试方案 - 第1节

1. CMA概述

CMA(Contiguous Memory Allocator,连续内存分配器)是 Linux 内核中用于解决大块连续物理内存分配问题的内存管理子系统。它在内核 3.5 版本中合入主线,旨在为 DMA(直接内存访问)、多媒体编解码、GPU 显存等需要物理连续缓冲区的设备驱动提供可靠的内存分配机制,同时尽可能降低对系统整体内存利用率的负面影响。

核心定位:CMA 不是替代伙伴系统(Buddy System)或 slab 分配器,而是作为它们之上的专用连续内存管理框架,在内存碎片化严重的系统中保障大块连续物理页的可用性。

1.1.1 为什么需要 CMA?

在长期运行的嵌入式 Linux 系统中,内存碎片化是一个普遍问题。伙伴系统虽然能高效管理 2 的幂次阶(order)页面,但随着频繁的分配与释放,物理内存会被分割成大量不连续的小块,导致无法满足高阶(如 order-4、order-8)连续页面的分配请求。传统解决方案包括预留大块内存(浪费严重)或使用 IOMMU/SMMU 做地址映射(增加硬件复杂度和延迟)。CMA 提供了一种更优雅的折中方案。

方案 优点 缺点
预留专用内存池 分配确定性高,无碎片问题 内存利用率低,预留区域空闲时也无法被其他模块使用
IOMMU/SMMU 映射 允许使用离散物理页,缓解连续需求 硬件依赖、TLB 开销、延迟增加
CMA 空闲时内存可被其他模块使用,分配时迁移页面保证连续 分配时可能触发页面迁移,引入延迟

1.2 CMA 解决内存碎片化的原理

CMA 的核心思想是“预留但不独占”。它在系统启动时从伙伴系统中预留一块物理连续区域(CMA 区域),但该区域在未被驱动使用时,可以像普通内存一样被匿名页、页缓存等可移动页面占用。当驱动需要连续内存时,CMA 分配器会将该区域内的可移动页面迁移到其他位置,从而“腾出”连续的物理空间。

关键机制:页面迁移(Page Migration)是 CMA 实现动态连续性的基础。只有被标记为 MIGRATE_MOVABLE 的页面才能被 CMA 迁移。因此,驱动在 CMA 区域中分配的页面必须符合可迁移属性。

1.2.1 工作流程

  1. 预留阶段:内核启动时,通过设备树(Device Tree)或内核参数 cma= 指定 CMA 区域的大小和起始地址。该区域被标记为 MIGRATE_CMA 类型,加入伙伴系统的可迁移页面列表。
  2. 空闲复用:在无 CMA 分配请求时,CMA 区域内的物理页被当作普通可移动页面,供用户空间进程、文件缓存等使用。
  3. 分配触发:驱动调用 dma_alloc_coherent() 或 cma_alloc() 请求连续内存时,CMA 分配器开始工作。
  4. 页面迁移:如果目标区域已被占用,分配器遍历该区域内的页面,将可移动页面逐页迁移到其他空闲区域,并更新页表映射。
  5. 分配完成:迁移完成后,CMA 区域内的页面全部空闲且连续,分配器将其分配给驱动使用。
CMA 工作流程与内存碎片化解原理 1. 预留 CMA 区域 2. 空闲时被复用 3. 驱动请求分配 4. 页面迁移:将可移动页面迁出 CMA 区域 遍历 → 逐页迁移 → 更新页表 → 回收物理页 5. 分配连续物理内存给驱动 碎片化场景 伙伴系统:大量 4KB 碎片,无法分配 1MB 连续块 CMA:通过迁移腾出连续空间 CMA 优势 内存利用率高:空闲时被复用 分配可靠:迁移保证连续性

1.3 CMA 在 Linux 内核中的作用

CMA 在内核中扮演着“连续内存的保障者”角色,其作用贯穿多个子系统:

  • DMA 引擎:为 DMA 描述符环、数据缓冲区提供物理连续内存,避免分散-聚集(SG)模式带来的性能开销。
  • 多媒体框架:V4L2(视频采集)、ALSA(音频)、DRM(显示)等驱动通常要求帧缓冲区物理连续,CMA 是首选分配器。
  • GPU 与加速器:部分嵌入式 GPU 没有 IOMMU,必须使用物理连续内存作为命令缓冲区或纹理内存。
  • 加密引擎:硬件加密加速器通常需要连续内存存放密钥和 IV 向量。
注意:CMA 分配可能触发直接回收(direct reclaim)和页面迁移,导致分配延迟达到毫秒级。对于实时性要求高的场景,建议在系统初始化阶段预分配 CMA 缓冲区,或使用 GFP_NOWAIT 标志避免阻塞。

1.3.1 CMA 与伙伴系统的关系

CMA 区域在伙伴系统中被标记为 MIGRATE_CMA 迁移类型。伙伴系统的页面迁移类型包括:

迁移类型 说明 是否可被 CMA 迁移
MIGRATE_UNMOVABLE 内核核心代码、页表、不可移动驱动内存 否
MIGRATE_MOVABLE 用户空间匿名页、页缓存、可移动驱动内存 是
MIGRATE_RECLAIMABLE 可回收的缓存(如 dentry、inode 缓存) 否(需先回收)
MIGRATE_CMA CMA 区域专用类型 —
MIGRATE_ISOLATE 临时隔离,用于内存热插拔或 CMA 迁移 —

1.4 CMA 配置与数据结构

CMA 的配置通常在设备树中完成,也可以通过内核参数动态调整。以下是典型的设备树配置示例:

// 设备树中定义 CMA 区域
reserved-memory {
    #address-cells = <2>;
    #size-cells = <2>;
    ranges;

    linux,cma {
        compatible = "shared-dma-pool";
        reusable;
        size = <0x0 0x10000000>;  // 256MB
        alignment = <0x0 0x200000>; // 2MB 对齐
        linux,cma-default;
    };
};

内核中 CMA 的核心数据结构是 struct cma,定义在 include/linux/cma.h:

struct cma {
    unsigned long   base_pfn;      // CMA 区域起始物理页帧号
    unsigned long   count;         // 总页数
    unsigned long   *bitmap;       // 位图,记录页面分配状态
    unsigned int    order_per_bit; // 每个位代表的阶数
    struct mutex    lock;
    const char      *name;
    struct page     *pages;        // 指向 page 数组的指针
};
关键字段说明:
base_pfn 和 count 定义了 CMA 区域的物理范围。
bitmap 以位图方式管理页面分配,order_per_bit 决定位图的粒度(通常为 0,即每页一位)。
pages 数组用于在迁移时快速定位页面结构。

1.5 CMA 的优缺点总结

维度 说明
优点 内存利用率高(空闲时被复用)、无需硬件 IOMMU、内核主线支持、配置灵活
缺点 分配延迟不确定(页面迁移耗时)、需要驱动配合使用可移动内存、CMA 区域大小需提前规划
适用场景 嵌入式 Linux、Android 设备、多媒体终端、工业控制、车载信息娱乐系统
不适用场景 硬实时系统(需确定性延迟)、已有 IOMMU 的服务器平台、内存极小的 MCU 系统
工程建议:在项目初期,通过 /proc/meminfo 中的 CmaTotal 和 CmaFree 字段监控 CMA 使用情况。如果 CmaFree 长期接近 0,说明 CMA 区域过小或驱动分配过于频繁,需要调整 CMA 大小或优化驱动分配策略。