3. CMA 内核配置:编译选项与区域参数
CMA(Contiguous Memory Allocator)的启用和行为完全由内核编译选项(Kconfig)和内核启动参数控制。嵌入式工程师必须在构建内核时正确配置 CONFIG_CMA 和 CONFIG_DMA_CMA,并合理设定 CMA 区域的大小与物理位置,才能让驱动层获得稳定的大块连续缓冲。
3.1 核心编译选项:CONFIG_CMA 与 CONFIG_DMA_CMA
CMA 功能通过两个层级的内核选项打开:
| 选项 | 依赖 | 作用 | 推荐值 |
|---|---|---|---|
CONFIG_CMA |
— | 启用 CMA 核心框架,提供 cma_alloc()/cma_release() 接口 |
y |
CONFIG_DMA_CMA |
CONFIG_CMA + CONFIG_DMA_CMA |
将 CMA 区域注册到 DMA 引擎,使 dma_alloc_coherent() 自动使用 CMA |
y |
CONFIG_CMA_AREAS |
CONFIG_CMA |
最大 CMA 区域数量(默认 7,最大 32) | 按需增加(如 12) |
CONFIG_CMA_SIZE_SEL_MBYTES |
CONFIG_DMA_CMA |
选择 CMA 区域大小的单位(MB) | y |
CONFIG_CMA_SIZE_MBYTES |
CONFIG_DMA_CMA |
默认 CMA 区域大小(MB),可被启动参数覆盖 | 视内存总量而定(如 64) |
关键依赖链: 只打开
CONFIG_CMA 而不打开 CONFIG_DMA_CMA,驱动无法通过标准 DMA API 自动使用 CMA 区域,必须手动调用 cma_alloc()。绝大多数嵌入式场景应同时开启两者。
3.2 CMA 区域大小与位置参数
CMA 区域的大小和物理起始地址通过内核启动参数 cma= 或设备树节点指定。参数格式如下:
# 内核 cmdline 方式(x86/ARM64 通用)
cma=size[MG]@start_address
# 示例:在 0x40000000 处分配 64MB CMA 区域
cma=64M@0x40000000
# 仅指定大小,由内核自动选择位置
cma=128M
在设备树中,CMA 区域通过 reserved-memory 节点定义:
/ {
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
linux,cma {
compatible = "shared-dma-pool";
reusable;
size = <0x0 0x04000000>; /* 64MB */
alignment = <0x0 0x200000>; /* 2MB 对齐 */
alloc-ranges = <0x0 0x40000000 0x0 0x10000000>;
linux,cma-default;
};
};
};
参数说明:
size:CMA 区域大小,必须为 2MB 的整数倍(与页面块大小对齐)。alignment:区域起始地址对齐,建议 2MB 或 1GB。alloc-ranges:限制 CMA 区域可分配的物理地址范围,避免占用关键外设地址空间。linux,cma-default:将此区域设为默认 CMA 池,驱动无需指定区域即可使用。
3.3 CMA 区域大小选择策略
CMA 区域过大会浪费低内存(Normal Zone),过小则无法满足大缓冲需求。下表给出典型场景的推荐值:
| 系统总内存 | 典型外设需求 | 推荐 CMA 大小 | 占比 |
|---|---|---|---|
| 256 MB | 单路 720p 摄像头 | 16–32 MB | 6–12% |
| 512 MB | 双路 1080p 摄像头 + GPU | 64–96 MB | 12–18% |
| 1 GB | 4K 视频编解码 + 多路 ISP | 128–256 MB | 12–25% |
| 2 GB+ | AI 加速器 + 高帧率多摄 | 256–512 MB | 12–25% |
避坑提示: CMA 区域不能超过
ZONE_NORMAL 的大小。在 32 位系统中,若 CMA 区域大于 896MB(典型 ZONE_NORMAL 上限),会导致内核直接映射区不足,引发 BUG: unable to handle kernel paging request。64 位系统通常无此限制。
3.4 CMA 区域位置与内存布局
下图展示了一个典型的 ARM64 系统内存布局,CMA 区域位于低端内存(DMA 可寻址范围)与高端内存之间:
3.5 多 CMA 区域配置
从 Linux 4.19 开始,内核支持多个 CMA 区域,每个区域可绑定特定外设。配置方式如下:
# 内核 cmdline 方式:逗号分隔多个区域
cma=64M@0x40000000,32M@0x50000000
# 设备树方式:定义多个 reserved-memory 节点
/ {
reserved-memory {
cma_camera: cma-camera {
compatible = "shared-dma-pool";
reusable;
size = <0x0 0x04000000>; /* 64MB */
alignment = <0x0 0x200000>;
alloc-ranges = <0x0 0x40000000 0x0 0x10000000>;
};
cma_gpu: cma-gpu {
compatible = "shared-dma-pool";
reusable;
size = <0x0 0x02000000>; /* 32MB */
alignment = <0x0 0x200000>;
alloc-ranges = <0x0 0x50000000 0x0 0x08000000>;
};
};
};
驱动通过 dma_alloc_coherent(dev, ...) 时,内核会根据 dev 的 dma-ranges 属性自动选择匹配的 CMA 区域。
3.6 运行时验证 CMA 配置
系统启动后,通过以下方式验证 CMA 是否生效:
# 查看 CMA 区域信息
cat /proc/meminfo | grep Cma
# 输出示例:
# CmaTotal: 65536 kB
# CmaFree: 63488 kB
# 查看 CMA 区域物理地址
dmesg | grep -i cma
# 输出示例:
# [ 0.000000] cma: Reserved 64 MiB at 0x0000000040000000
# 查看设备树 CMA 节点状态
ls /sys/kernel/debug/cma/
cat /sys/kernel/debug/cma/*/used
调试技巧: 若
CmaFree 远小于 CmaTotal,说明有驱动长期占用 CMA 页面未释放。使用 trace_cma_alloc 和 trace_cma_release 跟踪点可以定位泄漏源。
3.7 CMA 配置检查清单
- 确认内核版本:Linux 4.0+ 支持基本 CMA,4.19+ 支持多区域。
- 打开编译选项:
CONFIG_CMA=y、CONFIG_DMA_CMA=y、CONFIG_CMA_AREAS=12。 - 设定区域大小:根据外设需求计算,建议为 2MB 整数倍,不超过总内存 25%。
- 指定物理位置:通过
cma=或设备树alloc-ranges限制地址范围,避免与外设 MMIO 冲突。 - 验证启动日志:检查
dmesg中 CMA 保留信息,确认大小和地址正确。 - 监控运行时使用:通过
/proc/meminfo和 debugfs 跟踪 CMA 占用率。
生产环境建议: 在最终固件中,将 CMA 大小和位置固定为编译时默认值,避免依赖启动参数。使用设备树方式配置 CMA 区域,便于不同板型复用同一内核镜像。