DMA 基础概念

1.1 什么是 DMA

DMA 的全称是 Direct Memory Access,中文叫直接内存访问。说白了,就是让一块硬件独立完成内存和设备之间的数据搬运,CPU 站在旁边看着就行。

我刚开始做嵌入式的时候,以为外设传输数据都得 CPU 亲力亲为——读一个字节,写到内存,再读一个字节……效率极低。后来知道有 DMA 这个“搬运工”,才恍然大悟。

打个比方:CPU 是项目经理,DMA 就是仓库里的叉车。项目经理想搬十箱货,不用自己动手,喊一声“叉车去搬”,然后就可以干别的活了。叉车自己搞定从哪里搬、搬多少、放哪里,搬完了按喇叭通知一下。

我的个人经验: 第一次用 DMA 时,我还在寄存器里手动配源地址、目的地址、长度,然后等中断。后来才发现,现代 DMA 控制器都支持描述符链表,一次配好几十个任务,CPU 根本不用管中间过程。

1.2 DMA 的工作原理

DMA 的核心是一个专用的 DMA 控制器(简称 DMAC)。它内部有几个关键寄存器:

  • 源地址寄存器 – 从哪读数据
  • 目的地址寄存器 – 写到哪去
  • 传输计数寄存器 – 搬多少个字节/字/块
  • 控制寄存器 – 传输方向、宽度、突发模式、中断使能等

工作流程大致是这样:

  1. CPU 配置 DMA 控制器(设置源、目的、长度、模式)。
  2. 外设(比如 UART、SDIO)向 DMA 发出请求。
  3. DMA 向总线仲裁器申请总线控制权。
  4. 获得总线后,DMA 直接读外设数据,写到内存(或反之)。
  5. 每传完一个单元,计数减一。计数到零时,DMA 产生中断给 CPU。
  6. CPU 在中断里处理完成事件(或者检查描述符状态)。

这里有个容易翻车的地方:缓存一致性。我记得有一次项目里用 DMA 从摄像头搬数据,CPU 读到的图像总是花屏。查了一天,发现 DMA 写的是物理内存,但 CPU 的 data cache 里还藏着旧副本。后来加了 cache 刷新/无效化操作才解决。

避坑指南: 我曾经在 ARM Cortex-A 平台上直接启用 DMA 搬运网络包,没用 dma_map_single 做一致性映射,导致 CPU 看到的报文全是乱码。记住:但凡涉及 DMA 和 CPU 共享内存,一定要用 DMA API 处理 cache 一致性,别省这一步。

1.3 DMA 的应用场景

DMA 最常见的几个领域:

场景典型外设为什么需要 DMA
高速数据流以太网、USB 3.0、PCIe单包数据量大,CPU 逐字节处理会严重拖吞吐量
周期性批量传输音频 I2S、视频 CSI/DSI采样率固定,CPU 中断太频繁
内存到内存拷贝GPU 纹理上传、内存镜像用 DMA 比 CPU memcpy 快,而且不占 CPU 时间
多通道实时控制电机驱动 ADC 采样多个通道轮流采样,DMA 可以做成循环 buffer

我做过一个多通道数据采集项目,用单 DMA 加循环描述符,CPU 每 10ms 才醒来处理一次结果,CPU 占用率从 80% 降到了 5%。

1.4 DMA 与 CPU 的协作模式

实际的协作方式有三种常见形态:

  • 中断驱动 – DMA 完成一次传输就发中断,CPU 在中断里重新配置。适合小批量、频率低的情况。
  • 描述符链 – CPU 提前在内存里构建一个描述符链表,每个描述符指定一块传输任务。DMA 自动取下一个描述符,全部完成后才中断。这是 Linux 下网络、存储驱动的标配。
  • 循环 buffer – DMA 不停在环形缓冲区里搬数据,CPU 通过 head/tail 指针取用。音频驱动最爱用这个。
关键数据: 在 100Mbps 以太网上,一个 1500 字节的帧,如果中断处理每次 5μs,CPU 每秒要被打断 8300 次。用 DMA 描述符链后,CPU 只在收完一整批包(比如 256 个)时中断一次,中断频率降到 30 次/秒,省出来的时间可以跑上层协议栈。

下面这张图把 DMA 的工作流串联起来了:

DMA 数据搬移流程 CPU DMA 控制器 设备(外设) 内存(DDR) CPU 配好后,DMA 自己决定总线时序 搬完数据通过中断(IRQ)通知 CPU 配置寄存器 DMA 请求 读数据 写数据 IRQ

这张图里箭头标出了配置、请求、读写、中断的完整链条。你可以看到,CPU 只在开始和结束时参与,中间的数据搬移全由 DMA 自己跑。

嗯,到这里 DMA 的基础概念就差不多了。想深入的话,下一步就是看寄存器手册和内核的 dmaengine 框架,不过那是后面章节的事。你只要记住这几点:DMA 是硬件搬砖工;配好源、目的、长度就让它去跑;注意 cache 一致性;描述符链是高端玩法。