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工作原理框图 DMA工作原理框图 CPU DMA控制器 系统总线(AHB/AXI/PCIe) 源内存 (外设FIFO/内存缓冲区) 目标内存 (系统内存/外设寄存器) 配置描述符 DMA数据流 传输完成中断

DMA传输的基本流程包含以下步骤:

  1. 配置阶段:CPU在内存中构建DMA描述符,包含源地址、目标地址、传输长度、传输宽度、突发大小等参数,并将描述符地址写入DMA控制器的通道寄存器。
  2. 启动阶段:CPU向DMA控制器发送启动命令(通常通过写控制寄存器或门控位),DMA控制器开始接管总线。
  3. 传输阶段:DMA控制器按配置参数从源地址读取数据,写入目标地址,期间CPU可执行其他任务。
  4. 完成阶段:传输完成后,DMA控制器触发中断通知CPU,CPU在中断服务程序中处理数据并释放DMA通道。

1.2 DMA传输类型

根据数据源和目标的不同,DMA传输主要分为三种类型。理解每种类型的适用场景和硬件约束,是设计高效DMA内存池的前提。

传输类型 源端 目标端 典型场景 硬件约束
内存到内存 系统内存 系统内存 数据拷贝、缓冲区移动、内存整理 源和目标地址必须对齐;部分DMA不支持内存到内存模式
外设到内存 外设FIFO/寄存器 系统内存 网卡接收、音频采集、摄像头数据捕获 外设地址固定;需处理流控和超时
内存到外设 系统内存 外设FIFO/寄存器 网卡发送、音频播放、显示刷新 外设地址固定;需保证数据就绪后再启动
工程陷阱:许多嵌入式SoC的DMA控制器不支持内存到内存传输,或仅支持有限通道。例如,STM32的DMA1不支持内存到内存,而DMA2支持。在驱动开发前必须查阅芯片参考手册确认DMA能力矩阵。
DMA传输类型对比 DMA传输类型对比 内存 → 内存 源内存 目标内存 CPU配置描述符 DMA直接搬运 无外设参与 ⚠ 部分DMA不支持 外设 → 内存 外设FIFO 系统内存 外设产生数据 DMA轮询/硬件握手 CPU处理中断 ✓ 最常用模式 内存 → 外设 系统内存 外设FIFO CPU准备数据 DMA发送到外设 外设处理数据 ✓ 发送场景常用

1.3 DMA与CPU的协同工作模式

DMA与CPU的协同是嵌入式系统性能优化的核心。根据数据传输的控制方式和同步机制,常见的协同模式有以下四种:

模式 控制方式 CPU参与度 延迟 适用场景
轮询模式 CPU循环检查DMA状态寄存器 高(忙等) 低(无中断开销) 短传输、实时性要求极高
中断模式 DMA完成时触发中断 低(仅处理中断) 中等(中断延迟) 大多数通用场景
描述符链模式 DMA自动读取链表中的描述符 极低(批量配置) 低(硬件自动流转) 高速网络、视频流
双缓冲模式 两个缓冲区交替使用 低(乒乓切换) 极低(无等待) 音频、实时数据采集
工程建议:在Linux内核驱动中,推荐优先使用描述符链模式配合中断。对于网络设备,使用NAPI(New API)结合DMA描述符链,可以在高吞吐量下显著降低CPU占用率。对于音频设备,双缓冲模式(DMA乒乓缓冲)是标准实践。
DMA与CPU协同模式 DMA与CPU协同模式 时间 → 轮询模式 CPU DMA CPU DMA CPU不断检查DMA状态,占用100%核心 中断模式 CPU(空闲) DMA传输 ISR CPU DMA完成后触发中断,CPU响应处理 描述符链模式 CPU配置 DMA链式传输 中断(批量) DMA自动读取链表,批量传输后中断 CPU只需配置一次描述符链表 双缓冲模式(乒乓) Buf A Buf B Buf A Buf B DMA在A/B之间乒乓切换 CPU处理空闲缓冲区,无等待

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分配一致性内存。

描述符链表的典型工作流程:

  1. CPU在内存中分配一组描述符,每个描述符指向一个数据缓冲区。
  2. CPU将描述符通过link字段串联成单向链表,最后一个描述符的link字段指向NULL或自身(循环链表)。
  3. CPU将链表头地址写入DMA控制器的通道寄存器,启动传输。
  4. DMA控制器自动从链表头开始,依次读取每个描述符,执行对应的数据传输。
  5. 当DMA完成一个描述符后,自动加载下一个描述符,直到链表结束。
  6. 传输完成后,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的对齐要求和最大传输长度来规划缓冲区布局。