1. DMA基础概念
1.1 DMA工作原理
直接存储器访问(Direct Memory Access, DMA)是一种允许硬件子系统直接读写系统内存而无需CPU干预的数据传输技术。在嵌入式Linux系统中,DMA控制器作为总线上的独立主设备,能够接管总线控制权,在源地址与目标地址之间搬运数据,同时释放CPU去执行其他计算任务。
核心价值:DMA将CPU从繁重的数据拷贝工作中解放出来,使CPU专注于中断处理、协议解析、算法运算等高层次任务,显著提升系统吞吐量并降低延迟。
关键指标:DMA传输的典型延迟在微秒级,而CPU逐字节拷贝同样数据量的延迟通常在毫秒级。对于4KB数据块,DMA传输时间约为CPU拷贝时间的1/10~1/5。
DMA传输的基本流程包含以下步骤:
- 配置阶段:CPU在内存中构建DMA描述符,包含源地址、目标地址、传输长度、传输宽度、突发大小等参数,并将描述符地址写入DMA控制器的通道寄存器。
- 启动阶段:CPU向DMA控制器发送启动命令(通常通过写控制寄存器或门控位),DMA控制器开始接管总线。
- 传输阶段:DMA控制器按配置参数从源地址读取数据,写入目标地址,期间CPU可执行其他任务。
- 完成阶段:传输完成后,DMA控制器触发中断通知CPU,CPU在中断服务程序中处理数据并释放DMA通道。
1.2 DMA传输类型
根据数据源和目标的不同,DMA传输主要分为三种类型。理解每种类型的适用场景和硬件约束,是设计高效DMA内存池的前提。
| 传输类型 | 源端 | 目标端 | 典型场景 | 硬件约束 |
|---|---|---|---|---|
| 内存到内存 | 系统内存 | 系统内存 | 数据拷贝、缓冲区移动、内存整理 | 源和目标地址必须对齐;部分DMA不支持内存到内存模式 |
| 外设到内存 | 外设FIFO/寄存器 | 系统内存 | 网卡接收、音频采集、摄像头数据捕获 | 外设地址固定;需处理流控和超时 |
| 内存到外设 | 系统内存 | 外设FIFO/寄存器 | 网卡发送、音频播放、显示刷新 | 外设地址固定;需保证数据就绪后再启动 |
工程陷阱:许多嵌入式SoC的DMA控制器不支持内存到内存传输,或仅支持有限通道。例如,STM32的DMA1不支持内存到内存,而DMA2支持。在驱动开发前必须查阅芯片参考手册确认DMA能力矩阵。
1.3 DMA与CPU的协同工作模式
DMA与CPU的协同是嵌入式系统性能优化的核心。根据数据传输的控制方式和同步机制,常见的协同模式有以下四种:
| 模式 | 控制方式 | CPU参与度 | 延迟 | 适用场景 |
|---|---|---|---|---|
| 轮询模式 | CPU循环检查DMA状态寄存器 | 高(忙等) | 低(无中断开销) | 短传输、实时性要求极高 |
| 中断模式 | DMA完成时触发中断 | 低(仅处理中断) | 中等(中断延迟) | 大多数通用场景 |
| 描述符链模式 | DMA自动读取链表中的描述符 | 极低(批量配置) | 低(硬件自动流转) | 高速网络、视频流 |
| 双缓冲模式 | 两个缓冲区交替使用 | 低(乒乓切换) | 极低(无等待) | 音频、实时数据采集 |
工程建议:在Linux内核驱动中,推荐优先使用描述符链模式配合中断。对于网络设备,使用NAPI(New API)结合DMA描述符链,可以在高吞吐量下显著降低CPU占用率。对于音频设备,双缓冲模式(DMA乒乓缓冲)是标准实践。
1.4 DMA描述符与链表结构
在Linux内核中,DMA描述符通常由硬件定义的结构体表示。以下是一个典型的DMA描述符结构(以TI AM335x EDMA为例):
struct edma_desc {
u32 opt; /* 传输选项:同步模式、地址模式等 */
u32 src; /* 源地址 */
u32 a_b_cnt; /* A计数(帧内)和B计数(帧数) */
u32 dst; /* 目标地址 */
u32 src_dst_bidx; /* 源/目标地址索引增量 */
u32 link_bcntrld; /* 链接地址和B计数重载 */
u32 src_dst_cidx; /* 源/目标地址循环索引 */
u32 ccnt; /* C计数(帧组数) */
} __attribute__((packed));
描述符对齐要求:大多数DMA控制器要求描述符在内存中按32字节或64字节对齐。在分配描述符内存时,必须使用
kmem_cache_create创建对齐的slab缓存,或使用dma_alloc_coherent分配一致性内存。
描述符链表的典型工作流程:
- CPU在内存中分配一组描述符,每个描述符指向一个数据缓冲区。
- CPU将描述符通过
link字段串联成单向链表,最后一个描述符的link字段指向NULL或自身(循环链表)。 - CPU将链表头地址写入DMA控制器的通道寄存器,启动传输。
- DMA控制器自动从链表头开始,依次读取每个描述符,执行对应的数据传输。
- 当DMA完成一个描述符后,自动加载下一个描述符,直到链表结束。
- 传输完成后,DMA触发中断,CPU在中断中处理已完成的缓冲区,并重新填充描述符。
Cache一致性警告:当CPU修改描述符或数据缓冲区后,必须确保CPU cache与DMA看到的内存视图一致。在Linux中,使用
dma_map_single/dma_unmap_single或dma_alloc_coherent来管理一致性。直接使用kmalloc分配的内存进行DMA传输,在大多数架构上会导致数据损坏。这是后续章节的核心内容。
1.5 关键性能指标与选型考量
在实际工程中选择DMA传输策略时,需要关注以下指标:
| 指标 | 定义 | 典型值 | 影响因素 |
|---|---|---|---|
| 传输带宽 | 单位时间内传输的数据量 | 400 MB/s ~ 12 GB/s | 总线频率、数据宽度、突发大小 |
| 传输延迟 | 从启动到完成的时间 | 1 μs ~ 100 μs | 描述符加载时间、总线仲裁、中断延迟 |
| 通道数 | DMA控制器支持的独立传输通道 | 4 ~ 32 | SoC设计、外设数量 |
| 描述符深度 | 链表中可挂载的描述符数量 | 无限制(取决于内存) | 内存大小、描述符大小 |
| 对齐要求 | 源/目标地址必须满足的对齐粒度 | 4字节 / 8字节 / 32字节 | DMA控制器设计 |
选型原则:对于高带宽外设(如千兆以太网、MIPI摄像头),优先选择支持描述符链和多通道的DMA控制器,并确保总线带宽充足。对于低延迟场景(如I2S音频),双缓冲模式配合小粒度中断是最佳选择。在内存池设计中,需要根据DMA的对齐要求和最大传输长度来规划缓冲区布局。