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 区域生效的完整配置流程:
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,可能导致驱动预期地址与实际不符。