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计算与数据传输的并行执行,显著提升系统吞吐量。
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通过迁移这些页面来腾出连续空间。
2.4 CMA与DMA的协同工作模式
在多媒体驱动中,CMA与DMA的协同遵循以下典型流程:
- 驱动初始化阶段: 驱动调用
dma_alloc_coherent()或cma_alloc()从CMA区域分配物理连续内存。 - DMA映射阶段: 内核将分配的物理连续内存映射到DMA总线地址空间,返回给驱动一个DMA地址。
- 数据传输阶段: 驱动配置DMA控制器的源地址(外设寄存器)和目的地址(DMA地址),启动传输。
- 传输完成阶段: 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映射流程,才能设计出高效稳定的视频/音频数据通路。