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区域。设备树可以精确描述每个硬件模块(如ISP、编解码器)所需的独立CMA区域,避免不同驱动之间的内存竞争。启动参数 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区域注册的完整流程:

设备树CMA区域注册流程 内核解析DTB 扫描 reserved-memory 节点 检查 compatible = "shared-dma-pool" 检查 reusable 属性 调用 cma_declare_contiguous_nid() 注册 关键检查点 1. compatible 必须匹配 2. reusable 必须存在 3. reg 地址不能重叠

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区域在物理内存中的布局,以及对齐约束如何影响可用空间:

CMA区域对齐与大小约束示意图 系统物理内存空间 CMA区域1 (128MB) 2MB对齐 CMA区域2 (64MB) 2MB对齐 间隔 未使用 / 其他保留区 对齐与大小约束总结 1. 起始地址必须 2MB 对齐(CMA_MIN_ALIGNMENT_BYTES) 2. 区域大小必须是 4KB 页大小的整数倍 3. 不同CMA区域之间不能重叠,建议保留至少1页间隔 4. 总CMA大小不应超过系统内存的 10%~20%,否则影响常规内存分配 5. 使用 /proc/meminfo 中的 CmaTotal/CmaFree 验证配置是否生效

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;                                  /* 禁止内核映射 */
        };
    };
};
性能注意事项:为延迟敏感型外设(如VPU、显示控制器)分配独立的CMA区域,可以避免与其他驱动的内存分配产生竞争。但每个CMA区域都会占用额外的位图内存(每页约1位),过多的区域会增加内核管理开销。建议CMA区域数量不超过5个。