DMA 概述:从轮询到解放 CPU

做嵌入式这些年,我经常遇到一种尴尬场景:CPU 忙得要死,外设却在干等。说白了,CPU 大部分时间花在了搬运数据上,而不是处理数据。DMA 就是为解决这个问题诞生的。

DMA 是什么?

DMA(Direct Memory Access,直接存储器访问)是一种硬件机制。它允许外设和内存之间直接交换数据,不需要 CPU 逐字节参与。

  • 传统模式:CPU 从外设读一个字节,存到内存,再读下一个……CPU 被占死。
  • DMA 模式:CPU 配置好 DMA 控制器后,它自己完成搬运,CPU 可以干别的事。

举个例子。我在一个工业相机项目中,图像数据 10MB/s 持续涌入。如果用 CPU 中断逐字节搬,CPU 直接 100% 占用,系统别的事全停。后来换成 DMA,CPU 占用率降到 3%,还能跑算法。这就叫解放 CPU。

核心价值:DMA 把 CPU 从数据搬运的脏活累活中解放出来,让 CPU 去干它擅长的事——计算与控制。

DMA 发展简史:从纸带到高速总线

你可能觉得 DMA 是近代产物。其实它很老。

年代 里程碑 特点
1960s IBM 大型机首次引入 DMA 用于纸带/磁带的高速传输,纯硬件独立
1980s PC 行业标准 DMA(ISA 总线) Intel 8237 控制器,4 通道,8-bit 传输
1990s PCI 总线支持总线主控 DMA 外设可以直接发起 DMA,不再依赖 CPU 初始化每个 block
2000s~今 高性能系统 DMA(PCIe / AXI / NVMe) 多通道、描述符链、scatter-gather、cache 一致性

早期 DMA 很简单。ISA 时代的 8237 只有 4 个通道,一次最多传 64KB。我记得第一次调 ISA DMA,寄存器要写很繁琐……通道屏蔽、模式设置、字节数计数,一个不对就死机。现在想想,那会真是硬啃。

到了 PCI 时代,外设自己就能申请总线主控。DMA 不再只是“CPU 委托的搬运工”,而是可以主动发起的“智能搬运工”。这个变化很重要。

避坑指南:我曾经在一个 PCIe 设计中,因为没有处理好 DMA 描述符链的结束标记,结果传完最后一个 buffer 后直接 bus hang。后来强制在描述符最后加一个 end-of-chain 标志,问题解决。链式 DMA 的结束条件一定要确认。

DMA 在现代系统中的地位

现在你打开任何一台电脑、手机、路由器、智能汽车 ECU,里面都有 DMA。它已经不是可选项,而是基础组件。

  • 存储系统:NVMe SSD 通过 DMA 直接与内存交换数据,延迟从 ms 级降到 μs 级。
  • 网络:网卡的 RX/TX 描述符环,数据包直接从网卡 DMA 到应用缓冲区,零拷贝。
  • 多媒体:摄像头、显示控制器、音频 codec,全部通过 DMA 搬运帧数据。
  • 嵌入式实时系统:ADC 采样数据 DMA 到 RAM,CPU 只需定时处理,适合数据采集。

数据量大、实时性高的场景,没了 DMA 根本玩不转。你想想看,如果每路 4K 视频 60fps 数据都得 CPU 手动搬,CPU 再强也扛不住。

DMA 核心组件与关系

下面这张图是我经常画给团队新人的,能帮你快速理解 DMA 的骨架。

DMA 核心组件与数据流 CPU DMA 控制器 内存 (RAM) 外设/设备 系统总线 DMA 传输流程(简化) CPU 配置 DMA 控制器(源地址/目的地址/长度)→ DMA 通过总线与内存/外设交互 → 传输完成触发中断 数据传输路径:外设 ↔ DMA ↔ 内存,CPU 不参与数据搬运

从上到下走一遍:

  1. CPU 发指令到 DMA 控制器:源地址、目的地址、传输长度。
  2. DMA 控制器接管总线,从外设读数据,写到内存(或反过来)。
  3. 完成后,DMA 发中断告诉 CPU:“活干完了”。

关键点:CPU 只在开始和结束时介入,中间数据流完全由硬件走。这就是高效率的根源。

一点个人感悟

做过那么多项目,我越来越觉得 DMA 像是一个靠谱的硬件帮手。你告诉它要搬什么、搬哪里、搬多少,它就默默干完,不吵不闹。但你要是不给它配好描述符、不设对中断优先级,它也会翻车。嗯,后面我们会详细讲怎么驯服它。

注意:DMA 不是万能的。传输粒度很小(比如几个字节)时,DMA 初始化开销可能超过手动传输。小数据量还是用 CPU 搬划算。我见过有人把每个串口字符都用 DMA 传,结果性能反而下降。合适的场景才用 DMA。