DMA 概述与历史

说起 DMA,我得先跟你聊聊我刚入行那会儿的经历。那时候我还在做一款工业控制板,CPU 是 ARM9,主频不过 200MHz。板子上挂了一个百兆网卡和一个 SPI 接口的 Flash。项目调通之后,整机表现一直不太稳定——CPU 占用率动不动就飙到 90% 以上,系统响应慢得像蜗牛。

我当时排查了很久,最后发现罪魁祸首居然是数据搬运。CPU 每收到一个网络包,都要亲自把数据从网卡 FIFO 搬到内存里。一个包几百字节,一秒钟几千个包,CPU 几乎啥正事都干不了,光当搬运工了。

后来我在数据手册里翻到了 DMA 控制器的章节,花了三天时间把驱动从 PIO 模式改成了 DMA 模式。改完之后,CPU 占用率直接掉到了 30%。那一刻我真实地感受到——DMA 这东西,是真的能救命的。

1.1 DMA 技术发展历程

DMA 并不是一开始就有的。早期的计算机系统里,所有 I/O 操作都得 CPU 亲自下场。你想想看,CPU 既要跑应用逻辑,又得盯着外设的状态寄存器,还得把数据从一个地方搬到另一个地方。那个年代的 CPU,说白了就是个高级搬运工。

DMA 技术的发展,大致可以分成四个阶段。每个阶段都解决了一个核心痛点。

阶段 代表技术 核心改进 典型芯片/协议
第一阶段 轮询 / PIO CPU 直接控制 I/O,简单但低效 8255 PPI、通用 GPIO
第二阶段 简单 DMA 控制器 专用 DMA 芯片,CPU 只需配置启动 Intel 8237、MC6844
第三阶段 总线主控 DMA 设备直接控制总线,无需 CPU 介入 PCI Bus Master、AHB
第四阶段 描述符环 + IOMMU 多描述符队列、虚拟地址映射、缓存一致性 Intel VT-d、ARM SMMU、NVMe

第一个阶段是纯 CPU 搬运。外设有数据就触发中断,CPU 跑到中断服务程序里一个字节一个字节地读。我记得有一次调试一个低速串口,波特率 9600,一秒钟也就传 1KB 数据,CPU 就已经被打得满负荷了。那时候我就想,这事肯定有更聪明的办法。

第二个阶段,专用的 DMA 控制器出现了。CPU 只需要告诉 DMA 芯片三件事:数据从哪来、到哪去、传多少。剩下的搬运工作 DMA 自己干完,完事了再发个中断通知 CPU。这一下就把 CPU 从数据搬运里解放了出来。

第三个阶段是总线主控 DMA。设备自己掌握总线控制权,不需要通过 DMA 控制器中转。PCI 总线时代的 Bus Master 模式就是典型代表。网卡可以直接把数据写到内存里,CPU 完全不用管中间过程。

第四个阶段就是我们这门课的重点了。描述符环解决了多笔传输如何高效组织的问题,IOMMU 解决了虚拟地址与物理地址的映射和安全隔离问题,而缓存一致性则让 DMA 和 CPU 共享数据时不再出现数据错乱。这些技术组合在一起,才造就了今天千兆万兆网络、NVMe 固态硬盘和 GPU 高速计算的硬件基础。

1.2 为什么需要 DMA?

说白了,DMA 存在的理由只有一条:CPU 不适合做数据搬运。

CPU 最擅长的是逻辑判断、分支预测、复杂计算。搬运数据是机械重复的劳动,让 CPU 干这个,就像让火箭专家去搬砖——不是不能干,是太浪费了。

我习惯从三个维度来看 DMA 的价值:

  • 性能维度: 现代外设的速度已经远远超过了 CPU 的 I/O 处理能力。一个万兆网卡的理论吞吐是 10Gbps,也就是约 1.25GB/s。CPU 如果靠 PIO 模式处理这个数据量,主频再高也扛不住。
  • 效率维度: CPU 做数据搬运时,不能做任何其他事。这意味着系统的整体吞吐量被严重限制。DMA 把搬运工作外包出去之后,CPU 可以安心跑应用、做控制、处理协议栈。
  • 实时性维度: DMA 传输是硬件级别的,延迟可预测,不受 CPU 调度的影响。这对于音视频、工业控制等实时性要求高的场景尤其重要。

我做一个简单的对比,你一看就明白了。

对比项 PIO 模式 DMA 模式
CPU 占用率 高(80%~100%) 低(<10%)
数据传输吞吐 受限于 CPU 主频 接近总线带宽上限
延迟一致性 受中断响应影响大 硬件级低延迟,稳定
系统扩展性 差,CPU 成为瓶颈 好,可支持多外设并行
软件复杂度 低,直接读写寄存器 中等,需管理描述符环

嗯,看到这里你可能会问:那 DMA 是不是万能的?当然不是。DMA 也有它的代价——描述符管理、缓存一致性、中断 coalescing……这些都需要额外的设计和调试工作。但总的来说,在性能敏感的场景下,DMA 的优势是压倒性的。

1.3 典型应用场景

DMA 的应用几乎覆盖了所有高性能 I/O 场景。我挑三个最典型的来讲,这些也是我平时工作中打交道最多的。

网络

网络是 DMA 最大的用户之一。一个千兆网卡每秒能收 150 万个小包,每个包都要从网卡搬到内存。如果没有 DMA,CPU 早就被打死了。

现代的网卡普遍使用描述符环来管理 DMA 传输。每个描述符包含一个指向数据缓冲区的指针,网卡按照描述符的顺序自动完成数据搬运。CPU 只需要在初始时建好环,然后在收包时处理描述符的完成状态。

避坑经验: 我曾经在调试一个 FPGA 加速卡的时候,遇到了 DMA 描述符环的"环满"死锁问题。发送端把环填满了,但接收端因为处理不及时没有释放描述符,结果整个链路卡死了。后来我在驱动里加了环使用率的监控和背压机制,才彻底解决了这个问题。

存储

NVMe SSD 的出现,把存储性能推到了一个新的高度。PCIe 3.0 x4 的 NVMe 硬盘顺序读写可以跑到 3.5GB/s,这背后的核心技术之一就是 DMA。NVMe 的提交队列和完成队列本质上就是一对描述符环,CPU 把读写命令放到提交队列,NVMe 控制器通过 DMA 把数据搬入搬出,完成后在完成队列里写状态。

我记得以前调试 SATA SSD 的时候,AHCI 规范里也有 DMA,但 NCQ(原生命令队列)的深度只有 32。NVMe 把这个队列深度扩展到了 65536,配合多队列 DMA 描述符环,并发能力提升了几个数量级。

GPU

GPU 计算的核心流程是:CPU 把数据从主机内存搬到显存,GPU 做计算,然后再把结果搬回来。这个搬运动作就是通过 DMA 完成的。现代 GPU 支持双向 DMA 传输和异步传输流,让数据和计算可以完全重叠。

我用 CUDA 做图像处理的时候,经常用到 cudaMemcpyAsync——这个函数的核心就是在后台启动一个 DMA 传输,同时 CPU 和 GPU 可以继续干别的事。你想想看,如果没有 DMA,GPU 的利用率至少要打五折。

个人习惯: 我在设计 DMA 驱动时,通常会在描述符环里预留 1~2 个"哨兵"描述符,用来检测环的边界是否被正确维护。这个习惯帮我快速定位过好几次描述符索引越界的 bug,建议你也试试。

除了网络、存储和 GPU,DMA 在音频、视频采集、加密加速器、AI 推理芯片等场景里也扮演着核心角色。可以说,任何需要高速数据搬运的地方,都有 DMA 的影子。

本章要点回顾:

  • DMA 技术经历了从 PIO、简单 DMA 控制器、总线主控 DMA 到描述符环 + IOMMU 的演进
  • DMA 的核心价值是把 CPU 从数据搬运中解放出来,提升系统整体性能
  • 网络、存储、GPU 是 DMA 最典型的应用场景,每个场景都有独特的设计取舍
  • 描述符环是现代 DMA 的事实标准,也是缓存一致性问题的根源
DMA 技术演进与核心价值 轮询 / PIO 时代 CPU 亲自搬数据 简单 DMA 控制器 专用 DMA 芯片搬运 总线主控 DMA 设备直接控总线 描述符环 DMA 链式描述符管理 智能 DMA + IOMMU 虚拟化 + 缓存一致性 核心价值:CPU 解放,系统性能飞跃 CPU 从机械的数据搬运中解脱 → 专注逻辑控制、协议处理、应用计算 系统吞吐量提升 5~10 倍,延迟更稳定,外设可并行工作 从 CPU 亲自搬运到智能 DMA — 每一次演进都在做同一件事:让 CPU 干它该干的事 网络 存储 GPU