2. CMA配置与初始化
CMA(Contiguous Memory Allocator)的配置与初始化是驱动工程师在工程实践中必须掌握的基础环节。错误的配置会导致内存浪费、分配失败或系统性能下降。本章详细讲解三种核心配置方法:设备树(Device Tree)配置、内核启动参数配置,以及CMA区域的大小与对齐硬性要求。
2.1 CMA配置方法总览
CMA区域的配置入口分为静态配置与动态配置两大类。静态配置在系统启动前完成,适用于已知内存需求的嵌入式产品;动态配置则通过内核参数或模块参数在启动时调整。下表对比了三种主流配置方式的适用场景与优先级:
| 配置方式 | 配置入口 | 生效阶段 | 优先级 | 典型场景 |
|---|---|---|---|---|
| 设备树(Device Tree) | reserved-memory 节点 |
内核解析DTB时 | 高(覆盖内核默认值) | 多区域、多用途CMA,如VPU+GPU+Camera |
| 内核启动参数 | cma= 命令行参数 |
内核启动早期 | 中(被设备树覆盖) | 快速原型验证、单区域通用CMA |
| 内核Kconfig默认值 | CONFIG_CMA_SIZE_MBYTES |
内核编译时 | 低(默认兜底) | 无设备树、无启动参数时的后备方案 |
cma= 更适合开发阶段的快速调试。
2.2 设备树中CMA区域的配置方法
设备树是嵌入式Linux系统中描述硬件资源的标准方式。CMA区域在设备树中通过 reserved-memory 节点定义,每个CMA子节点代表一个独立的连续内存区域。
2.2.1 基本设备树节点结构
以下是一个典型的设备树CMA配置片段,定义了两个独立的CMA区域:一个用于视频编解码器(vcodec),一个用于GPU:
/ {
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
/* 视频编解码器专用CMA区域 */
cma_vcodec: cma_vcodec_region {
compatible = "shared-dma-pool";
reg = <0x0 0xA0000000 0x0 0x10000000>; /* 256MB @ 0xA0000000 */
reusable;
linux,cma-default;
};
/* GPU专用CMA区域 */
cma_gpu: cma_gpu_region {
compatible = "shared-dma-pool";
reg = <0x0 0xB0000000 0x0 0x08000000>; /* 128MB @ 0xB0000000 */
reusable;
};
};
/* 引用CMA区域的设备节点 */
vcodec: video-codec@A1000000 {
compatible = "vendor,video-codec";
reg = <0x0 0xA1000000 0x0 0x1000>;
memory-region = <&cma_vcodec>;
};
gpu: gpu@B1000000 {
compatible = "vendor,gpu";
reg = <0x0 0xB1000000 0x0 0x1000>;
memory-region = <&cma_gpu>;
};
};
2.2.2 关键属性说明
| 属性 | 值类型 | 含义 | 是否必选 |
|---|---|---|---|
compatible |
字符串 | 必须为 "shared-dma-pool",表示该区域用于DMA缓冲 |
是 |
reg |
<地址 长度> | 物理起始地址和区域大小(字节) | 是 |
reusable |
空属性 | 标记该区域可被内核回收复用(CMA核心语义) | 是 |
linux,cma-default |
空属性 | 将该区域设为系统默认CMA区域(最多一个) | 否 |
alignment |
<u64> | 指定区域对齐要求(字节),默认对齐到页大小 | 否 |
no-map |
空属性 | 禁止内核建立页表映射(用于安全/硬件保留区) | 否 |
reusable 属性。如果 reserved-memory 节点没有 reusable,内核会将其视为静态保留区,不会纳入CMA管理,驱动调用 dma_alloc_coherent 时无法从该区域分配内存。
2.2.3 设备树CMA配置流程图
下图展示了从设备树解析到CMA区域注册的完整流程:
2.3 内核启动参数 cma= 的配置方法
内核启动参数 cma= 提供了一种无需修改设备树的快速配置方式,适合开发调试阶段。其语法格式为:
cma=<size>[M|G]@<start-address>
参数说明:
- size:CMA区域大小,支持后缀
M(兆字节)和G(吉字节)。例如cma=256M表示256MB。 - start-address(可选):指定物理起始地址。例如
cma=128M@0xA0000000表示在0xA0000000处分配128MB。 - 如果不指定起始地址,内核会在ZONE_DMA或ZONE_NORMAL中自动选择合适的位置。
2.3.1 实际应用示例
在U-Boot或GRUB的启动命令行中添加:
# 仅指定大小,由内核自动选择地址
cma=256M
# 指定大小和起始地址
cma=128M@0xA0000000
# 同时使用设备树和启动参数(设备树优先级更高)
cma=64M # 设备树中若已定义cma_default,此参数将被忽略
linux,cma-default 属性的CMA区域,内核启动参数 cma= 会被完全忽略。只有设备树中没有默认CMA区域时,启动参数才会生效。这一设计是为了保证产品级配置的确定性。
2.4 CMA区域大小与对齐要求
CMA区域的大小和对齐是影响系统稳定性和内存利用率的关键因素。错误的对齐会导致内核报错 "CMA: failed to reserve ...",而大小设置不当则可能引发分配失败或内存浪费。
2.4.1 对齐硬性要求
CMA区域的对齐要求由内核的 cma_declare_contiguous_nid() 函数强制实施,具体规则如下:
| 参数 | 对齐要求 | 说明 |
|---|---|---|
| 区域起始地址 | 必须对齐到 CMA_MIN_ALIGNMENT_BYTES(通常为 2MB) |
由 CONFIG_CMA_ALIGNMENT 控制,默认2MB |
| 区域大小 | 必须是 页大小(4KB) 的整数倍 | 内核内部以页为单位管理CMA |
| 位图对齐 | 位图大小按 8字节 对齐 | 位图用于跟踪CMA区域内页面的分配状态 |
| 跨区域对齐 | 不同CMA区域不能重叠,间隔至少1页 | 由设备树 reg 属性保证 |
2.4.2 大小选择策略
CMA区域的大小需要根据实际驱动需求进行估算。以下是一个典型的多媒体系统CMA大小分配参考:
| 硬件模块 | 典型分辨率/负载 | 推荐CMA大小 | 说明 |
|---|---|---|---|
| 视频解码器(H.264/H.265) | 4K@60fps | 64MB ~ 128MB | 参考帧缓冲区 + 码流缓冲区 |
| 图像信号处理器(ISP) | 4800万像素 | 32MB ~ 64MB | RAW + YUV 多帧缓存 |
| GPU(3D渲染) | 1080p 游戏 | 128MB ~ 256MB | 帧缓冲 + 纹理 + 顶点缓冲 |
| 显示控制器(Display) | 4K@60fps 双屏 | 32MB ~ 64MB | Framebuffer + 图层缓冲区 |
| 音频DSP | 多通道音频处理 | 4MB ~ 16MB | 音频帧缓冲 + 算法工作区 |
2.4.3 对齐与大小关系示意图
下图展示了CMA区域在物理内存中的布局,以及对齐约束如何影响可用空间:
2.5 验证CMA配置是否生效
配置完成后,可以通过以下方法验证CMA区域是否正确初始化:
2.5.1 内核启动日志
在dmesg中搜索CMA相关的输出:
# 查看CMA区域注册信息
dmesg | grep -i cma
# 典型输出示例
[ 0.000000] cma: Reserved 256 MiB at 0x00000000a0000000
[ 0.000000] cma: Reserved 128 MiB at 0x00000000b0000000
2.5.2 /proc/meminfo 检查
# 查看CMA统计信息
cat /proc/meminfo | grep -i cma
# 输出示例
CmaTotal: 393216 kB # 总CMA大小(所有区域之和)
CmaFree: 393216 kB # 当前空闲CMA大小
2.5.3 设备树调试接口
# 查看设备树中reserved-memory节点的解析结果
ls -la /sys/firmware/devicetree/base/reserved-memory/
cat /sys/firmware/devicetree/base/reserved-memory/cma_vcodec_region/reg
CmaTotal 显示为0,说明CMA区域没有成功注册。常见原因包括:设备树中缺少 reusable 属性、compatible 拼写错误、或物理地址与现有内存映射冲突。此时应优先检查dmesg中的 "CMA: failed to reserve" 错误信息。
2.6 多CMA区域配置的最佳实践
在复杂的嵌入式系统中,单一CMA区域往往无法满足所有外设的需求。以下是一个经过验证的多区域配置模板:
/ {
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
/* 系统默认CMA区域(通用用途) */
cma_default: cma_default_region {
compatible = "shared-dma-pool";
reg = <0x0 0x90000000 0x0 0x10000000>; /* 256MB */
reusable;
linux,cma-default;
};
/* 延迟敏感型:VPU专用区域(低延迟、固定地址) */
cma_vpu: cma_vpu_region {
compatible = "shared-dma-pool";
reg = <0x0 0xA0000000 0x0 0x08000000>; /* 128MB */
reusable;
alignment = <0x0 0x200000>; /* 2MB对齐 */
};
/* 大块分配型:GPU专用区域(大块连续内存) */
cma_gpu: cma_gpu_region {
compatible = "shared-dma-pool";
reg = <0x0 0xA8000000 0x0 0x10000000>; /* 256MB */
reusable;
};
/* 安全隔离型:加密引擎专用区域(不映射到CPU页表) */
cma_crypto: cma_crypto_region {
compatible = "shared-dma-pool";
reg = <0x0 0xB8000000 0x0 0x02000000>; /* 32MB */
reusable;
no-map; /* 禁止内核映射 */
};
};
};