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 可寻址范围)与高端内存之间:

CMA 区域在系统内存布局中的位置 CMA 区域在系统内存布局中的位置 内核代码/数据段 DMA ZONE (低端) CMA 区域 NORMAL ZONE (可回收页面 + 匿名页) 可回收/迁移 CMA 区域通常位于 DMA ZONE 之上、NORMAL ZONE 之内,确保外设可寻址

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 配置检查清单

  1. 确认内核版本:Linux 4.0+ 支持基本 CMA,4.19+ 支持多区域。
  2. 打开编译选项:CONFIG_CMA=y、CONFIG_DMA_CMA=y、CONFIG_CMA_AREAS=12。
  3. 设定区域大小:根据外设需求计算,建议为 2MB 整数倍,不超过总内存 25%。
  4. 指定物理位置:通过 cma= 或设备树 alloc-ranges 限制地址范围,避免与外设 MMIO 冲突。
  5. 验证启动日志:检查 dmesg 中 CMA 保留信息,确认大小和地址正确。
  6. 监控运行时使用:通过 /proc/meminfo 和 debugfs 跟踪 CMA 占用率。
生产环境建议: 在最终固件中,将 CMA 大小和位置固定为编译时默认值,避免依赖启动参数。使用设备树方式配置 CMA 区域,便于不同板型复用同一内核镜像。