1. DMA基础概念
1.1 DMA工作原理
DMA(Direct Memory Access,直接存储器访问)是一种允许硬件子系统(外设)直接读写系统内存而无需CPU干预的数据传输技术。在DMA传输过程中,CPU只需初始化传输任务,随后由DMA控制器(DMAC)负责实际的数据搬运,传输完成后通过中断通知CPU。
核心要点:DMA使外设与内存之间建立“直通”数据通道,CPU从逐字节的数据搬运中解放出来,专注于计算与控制任务。
DMA传输的典型流程包含以下步骤:
- 外设请求:外设(如UART、SPI、ADC)准备好数据或需要数据时,向DMA控制器发送请求信号。
- CPU初始化:CPU配置DMA控制器的源地址、目的地址、传输长度、传输方向等参数。
- DMA传输:DMA控制器获得总线控制权,在源与目的之间搬运数据,每次传输后更新地址计数器。
- 传输完成:当计数器归零,DMA控制器向CPU发送中断信号,通知传输结束。
- CPU处理:CPU在中断服务程序中处理数据或启动下一轮传输。
1.2 DMA与CPU数据搬运对比
传统的数据搬运方式(PIO,Programmed I/O)由CPU逐字或逐字节地执行加载/存储指令完成。DMA方式则将搬运任务卸载给专用硬件。两者在多个维度存在显著差异:
| 对比维度 | CPU搬运(PIO) | DMA搬运 |
|---|---|---|
| 执行主体 | CPU核心 | DMA控制器(DMAC) |
| CPU占用率 | 高(传输期间CPU被完全占用) | 低(仅初始化与中断处理时占用) |
| 传输速率 | 受限于CPU指令执行速度 | 接近总线带宽,通常快3~10倍 |
| 适用场景 | 小数据量(< 64字节)、控制寄存器访问 | 大数据块(≥ 64字节)、流式外设 |
| 硬件开销 | 无额外硬件需求 | 需要DMA控制器、总线仲裁逻辑 |
| 编程复杂度 | 简单,直接读写外设寄存器 | 较高,需管理描述符、缓存一致性 |
| 功耗 | 较高(CPU持续活跃) | 较低(CPU可进入低功耗模式) |
工程经验:在嵌入式Linux中,对于小于64字节的短数据(如GPIO状态读取、配置寄存器写入),PIO方式反而更优,因为DMA的初始化开销(描述符设置、缓存刷新)可能超过搬运本身的时间。
1.3 DMA在嵌入式系统中的作用
在嵌入式系统中,DMA扮演着性能倍增器和功耗优化器的双重角色。以下是DMA在嵌入式领域的核心应用场景:
- 高速数据采集:ADC采样数据通过DMA直接存入内存环形缓冲区,CPU仅在缓冲区满时批量处理,避免逐点中断。
- 显示刷新:LCD/OLED控制器通过DMA从帧缓冲区搬运像素数据,实现60fps流畅显示而CPU几乎零负载。
- 音频流处理:I²S音频接口使用DMA双缓冲(ping-pong buffer)机制,确保音频数据连续无断裂。
- 网络数据包收发:以太网MAC控制器利用DMA描述符环高效收发网络帧,是Linux网络协议栈的底层基石。
- 存储设备访问:eMMC/SD卡控制器通过DMA搬运数据块,实现数十MB/s的读写速率。
- 外设间数据传输:DMA支持内存到内存、内存到外设、外设到内存三种传输方向,可连接任意两个支持DMA的模块。
注意:在嵌入式Linux系统中,DMA的使用必须考虑缓存一致性(Cache Coherency)问题。CPU缓存与DMA直接访问的内存之间可能存在数据不一致,这是流式DMA映射API(dma_map_single/dma_unmap_single等)要解决的核心问题,后续章节将详细展开。
1.4 DMA传输的关键参数
理解DMA传输需要掌握以下核心参数,这些参数将直接映射到Linux DMA API的调用参数中:
| 参数 | 说明 | 典型值/范围 |
|---|---|---|
| 源地址 | 数据传输的起始地址(内存或外设寄存器地址) | 32位/64位物理地址 |
| 目的地址 | 数据传输的目标地址 | 32位/64位物理地址 |
| 传输长度 | 单次DMA传输的字节数 | 1 ~ 65535字节(取决于控制器) |
| 传输宽度 | 每次总线传输的数据位宽 | 8位、16位、32位、64位 |
| 传输方向 | 内存到外设、外设到内存、内存到内存 | MEM_TO_DEV / DEV_TO_MEM / MEM_TO_MEM |
| 突发传输 | 连续传输的数据单元个数(Burst Size) | 1、4、8、16(取决于总线协议) |
| 循环模式 | 传输完成后自动重置地址计数器,实现连续传输 | 启用/禁用 |
| 中断使能 | 传输完成/错误时是否产生中断 | 启用/禁用 |
实践提示:在编写Linux DMA驱动程序时,
dma_slave_config结构体中的src_addr_width、dst_addr_width、src_maxburst、dst_maxburst等字段直接对应上表中的传输宽度和突发传输参数。配置错误会导致数据传输错位或性能下降。
1.5 DMA传输模式分类
根据DMA控制器的实现方式,DMA传输可分为以下三种模式:
- 寄存器直接模式(Register-based DMA):CPU直接读写DMA控制器的源/目的地址寄存器,适用于简单、低性能场景。
- 描述符链模式(Descriptor-based DMA):在内存中构建描述符链表,每个描述符包含传输参数和指向下一个描述符的指针。DMA控制器自动遍历链表,支持复杂、多段传输。
- 帧模式(Frame-based DMA):专为音视频等流式数据设计,支持帧起始/结束同步信号,自动处理帧边界。
在Linux内核中,描述符链模式是主流实现方式,virt-dma(虚拟DMA)框架和dmaengine API均基于描述符设计。流式DMA映射API(dma_map_single/dma_unmap_single等)正是为描述符模式下的缓存一致性管理而生的。