4. CMA内核配置

CMA 的使能与区域定义完全由 Linux 内核的 Kconfig 编译选项和内核启动参数控制。本节将详细说明 CONFIG_CMA、CONFIG_DMA_CMA 两个关键编译选项的语义,以及通过内核 cmdline 配置 CMA 区域大小与起始地址的标准方法。

4.1 内核编译选项:CONFIG_CMA 与 CONFIG_DMA_CMA

在 Linux 内核源码树中,CMA 相关的配置项位于 Memory Management options 和 Device Drivers → Generic Driver Options 路径下。下表列出了两个核心选项的依赖关系与功能差异:

配置项 依赖 功能描述 推荐状态
CONFIG_CMA CONFIG_MMU 启用 CMA 核心框架,提供 cma_alloc() / cma_release() 等 API 必须开启
CONFIG_DMA_CMA CONFIG_CMA && CONFIG_DMA_CMA 为 DMA 映射层自动预留 CMA 区域,驱动可通过 dma_alloc_coherent() 透明使用 强烈建议开启

配置检查命令: 在已编译的内核中,可通过以下命令确认选项是否生效:

# 检查 CONFIG_CMA 是否启用
zcat /proc/config.gz | grep CONFIG_CMA

# 检查 CMA 区域总大小(单位:字节)
cat /sys/kernel/debug/cma/usage

开启 CONFIG_DMA_CMA 后,内核会创建一个默认的全局 CMA 区域,其大小由 CMA_SIZE_MBYTES 或内核 cmdline 参数 cma= 决定。若未显式配置,内核会根据物理内存总量自动计算一个默认值(通常为总内存的 2%~5%)。

4.2 CMA 区域大小与起始地址的配置方法

CMA 区域的物理地址范围通过内核启动参数(cmdline)传递,支持两种配置模式:仅指定大小 和 同时指定大小与起始地址。

4.2.1 仅指定大小(自动分配起始地址)

当只关心 CMA 区域大小而不关心物理位置时,使用 cma= 参数:

# 在 bootloader 的 cmdline 中添加
cma=256M

# 含义:预留 256 MiB 的连续物理内存作为 CMA 区域
# 内核会自动在 ZONE_DMA 或 ZONE_NORMAL 中寻找空闲区间

4.2.2 同时指定大小与起始地址(精确控制)

对于需要固定物理地址的场景(如外设 DMA 地址范围受限),使用 cma= 的完整语法:

# 格式:cma=<size>@<start_address>
cma=128M@0x40000000

# 含义:在物理地址 0x40000000 处预留 128 MiB 作为 CMA 区域
# 起始地址必须与 CMA 对齐要求一致(通常为 2 MiB 或 4 MiB 对齐)

对齐约束: CMA 区域的起始地址和大小必须满足 CMA_MIN_ALIGNMENT_BYTES(通常为 2 MiB)对齐。若未对齐,内核会在启动时自动向下取整对齐,可能导致实际区域比预期略小。建议在 bootloader 脚本中手动对齐:

# 示例:确保 0x40000000 是 2 MiB 对齐
# 0x40000000 % 0x200000 == 0  ✅ 合法

4.3 多 CMA 区域配置(高级用法)

从 Linux 4.19 开始,内核支持通过 Device Tree 或 ACPI 定义多个 CMA 区域,每个区域可绑定到特定的设备或内存节点。以下是一个典型的 Device Tree 配置片段:

// arch/arm/boot/dts/my-board.dts
reserved-memory {
    #address-cells = <2>;
    #size-cells = <2>;
    ranges;

    /* 全局 CMA 区域 */
    linux,cma {
        compatible = "shared-dma-pool";
        reusable;
        size = <0x0 0x10000000>;  // 256 MiB
        alignment = <0x0 0x200000>; // 2 MiB 对齐
        linux,cma-default;
    };

    /* 专用 CMA 区域:为 VPU 预留 */
    vpu_cma: vpu_cma@0xA0000000 {
        compatible = "shared-dma-pool";
        reusable;
        reg = <0x0 0xA0000000 0x0 0x08000000>; // 128 MiB @ 0xA0000000
    };
};

// 在 VPU 设备节点中引用
vpu: vpu@A2000000 {
    memory-region = <&vpu_cma>;
};

4.4 CMA 区域配置流程 SVG 图

下图展示了从内核编译选项到最终 CMA 区域生效的完整配置流程:

CMA 区域配置流程 步骤 1:内核编译配置 CONFIG_CMA=y CONFIG_DMA_CMA=y 内核 cmdline 参数 cma=256M 或 cma=128M@0x40000000 步骤 2:内核启动解析 cma_early_init() → cma_region_register() 创建全局 cma_areas[] 数组 步骤 3:驱动通过 API 分配

4.5 配置验证与调试

系统启动后,可通过以下接口验证 CMA 配置是否生效:

接口路径 内容说明 示例输出
/proc/meminfo 显示 CmaTotal 和 CmaFree 字段 CmaTotal: 262144 kB
/sys/kernel/debug/cma/usage 每个 CMA 区域的名称、大小、使用量 global: size=256MiB, used=0MiB
dmesg | grep cma 内核启动时 CMA 初始化日志 cma: Reserved 256 MiB at 0x40000000

最佳实践: 在产品开发阶段,建议在 cmdline 中同时添加 cma_debug=1 参数,这会启用 CMA 分配/释放的详细跟踪日志,帮助定位驱动与 CMA 交互时的时序问题。

4.6 常见配置错误与避坑

  • 区域过大导致内核 OOM: CMA 区域从系统可用内存中扣除,若设置超过总内存的 30%,可能导致非 CMA 内存不足。建议控制在 5%~15%。
  • 起始地址与硬件 DMA 范围冲突: 某些外设的 DMA 地址范围受限(如只能访问 32 位地址空间),CMA 区域必须落在该范围内,否则 dma_alloc_coherent() 会失败。
  • 多区域重叠: 使用 Device Tree 定义多个 CMA 区域时,必须确保 reg 属性指定的地址范围不重叠,否则内核启动时会报错并拒绝注册。
  • 对齐错误: 若 cmdline 中 cma=128M@0x40001000(未 2 MiB 对齐),内核会自动修正为 0x40000000,实际区域偏移了 4 KiB,可能导致驱动预期地址与实际不符。