2. CMA与DMA的关系

在多媒体驱动开发中,DMA(Direct Memory Access,直接内存访问)是硬件设备与系统内存之间进行高速数据传输的核心机制。而CMA(Contiguous Memory Allocator,连续内存分配器)则是Linux内核为满足DMA对物理连续内存需求而设计的专用内存管理模块。理解两者之间的关系,是掌握多媒体驱动内存配置的关键。

2.1 DMA的工作原理

DMA是一种允许硬件设备直接读写系统内存而无需CPU逐字节参与的数据传输技术。在传统的PIO(Programmed I/O)模式下,CPU需要执行指令来搬运每个数据单元,这会严重占用CPU时间。DMA控制器接管了数据搬运任务,CPU只需初始化传输参数,然后可以继续执行其他任务,传输完成后由DMA控制器发送中断通知CPU。

DMA传输的核心优势: 将CPU从数据搬运中解放出来,实现CPU计算与数据传输的并行执行,显著提升系统吞吐量。
DMA数据传输工作流程 CPU 初始化传输参数 DMA控制器 接管数据搬运 系统内存 数据缓冲区 外设设备 摄像头/GPU/网卡 ① 配置源/目的地址 ② 读取/写入数据 ③ 数据流 ④ 传输完成中断 DMA传输期间CPU可执行其他任务,传输完成后通过中断通知CPU

2.2 为什么DMA需要物理连续内存

DMA控制器与CPU不同,它没有MMU(内存管理单元),无法进行虚拟地址到物理地址的转换。DMA控制器直接操作物理地址总线,因此它要求传输缓冲区在物理地址空间中是连续的。这一限制源于DMA控制器的硬件设计:

特性 CPU访问内存 DMA访问内存
地址转换能力 通过MMU支持虚拟地址→物理地址映射 无MMU,直接使用物理地址
内存连续性要求 虚拟地址连续即可,物理页可分散 物理地址必须连续
传输粒度 按页(4KB)访问 按字节/字/块连续访问
最大传输长度 受虚拟地址空间限制 受物理连续块大小限制
典型场景 应用程序内存访问 多媒体帧缓冲、网络数据包
关键约束: 如果DMA缓冲区在物理上不连续,DMA控制器在跨越页边界时会访问到错误的物理地址,导致数据损坏或系统崩溃。这是多媒体驱动开发中最常见的错误来源之一。

以摄像头驱动为例,当摄像头传感器采集一帧图像时,DMA控制器需要将图像数据连续写入内存缓冲区。如果缓冲区在物理上分散在多个不连续的页中,DMA控制器无法自动跳转到下一页,传输将失败。

2.3 CMA如何为DMA提供连续内存

CMA(Contiguous Memory Allocator)是Linux内核专门为解决DMA连续内存需求而设计的内存管理机制。它在系统启动时预留一块大的物理连续内存区域,平时允许可移动页面(如匿名页、文件缓存)占用该区域,当DMA设备需要连续内存时,CMA通过迁移这些页面来腾出连续空间。

CMA内存分配与回收流程 阶段1:系统启动时预留CMA区域(物理连续) 阶段2:正常运行时,可移动页面占用CMA区域 阶段3:DMA设备请求连续内存 → 触发页面迁移 页面迁移过程 ① 将CMA中的可移动页 复制到非CMA区域 连续内存释放 ② 原页面被释放 形成物理连续空闲块 阶段4:DMA设备获得物理连续内存,开始数据传输

2.4 CMA与DMA的协同工作模式

在多媒体驱动中,CMA与DMA的协同遵循以下典型流程:

  1. 驱动初始化阶段: 驱动调用 dma_alloc_coherent() 或 cma_alloc() 从CMA区域分配物理连续内存。
  2. DMA映射阶段: 内核将分配的物理连续内存映射到DMA总线地址空间,返回给驱动一个DMA地址。
  3. 数据传输阶段: 驱动配置DMA控制器的源地址(外设寄存器)和目的地址(DMA地址),启动传输。
  4. 传输完成阶段: DMA控制器发送中断,驱动处理数据后调用 dma_free_coherent() 释放内存回CMA池。
最佳实践: 在多媒体驱动中,建议在系统启动时通过内核参数 cma=size[MG] 预留足够大的CMA区域(例如摄像头1080p@30fps需要至少4MB连续内存)。对于需要频繁分配/释放的场景,使用DMA缓冲池(DMA Buffer Pool)可以避免反复迁移的开销。

2.5 CMA与DMA的配置参数对比

配置项 内核参数/API 说明 典型值
CMA区域大小 cma=size[MG] 系统启动时预留的连续内存总量 64MB~256MB(多媒体设备)
CMA区域起始地址 cma=start@size 指定CMA区域的物理起始地址 0x40000000@64M
DMA一致性分配 dma_alloc_coherent() 分配DMA可访问的物理连续+缓存一致性内存 返回虚拟地址和DMA地址
DMA流式映射 dma_map_single() 将已有缓冲区映射到DMA地址空间 需处理缓存一致性
CMA对齐要求 CMA_ALIGNMENT CMA分配的对齐粒度(页大小倍数) 4KB(默认)

2.6 典型多媒体场景中的CMA与DMA

以嵌入式摄像头驱动为例,CMA与DMA的协作关系如下:

/* 摄像头驱动中CMA+DMA的典型使用模式 */
static int camera_start_capture(struct camera_dev *dev)
{
    dma_addr_t dma_addr;
    void *cpu_addr;
    size_t frame_size = 1920 * 1080 * 2; /* 1080p YUYV格式 */

    /* 1. 从CMA区域分配物理连续内存 */
    cpu_addr = dma_alloc_coherent(dev->dev, frame_size,
                                  &dma_addr, GFP_KERNEL);
    if (!cpu_addr)
        return -ENOMEM;

    /* 2. 配置DMA控制器:外设→内存传输 */
    dma_engine_slave_config(dev->dma_chan, &config);
    dma_desc = dmaengine_prep_slave_sg(dev->dma_chan,
                                       &dma_sg, 1, DMA_DEV_TO_MEM,
                                       DMA_PREP_INTERRUPT);

    /* 3. 启动DMA传输 */
    dmaengine_submit(dma_desc);
    dma_async_issue_pending(dev->dma_chan);

    /* 4. 传输完成后释放内存回CMA */
    dma_free_coherent(dev->dev, frame_size, cpu_addr, dma_addr);
    return 0;
}
关键总结: CMA是Linux内核为DMA设备提供物理连续内存的核心机制。它通过“预留+按需迁移”的策略,在保证DMA硬件需求的同时,避免了传统连续内存分配方式导致的内存浪费。多媒体驱动开发者需要理解CMA的配置参数、分配API以及DMA映射流程,才能设计出高效稳定的视频/音频数据通路。