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 只需要在初始时建好环,然后在收包时处理描述符的完成状态。
存储
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 的事实标准,也是缓存一致性问题的根源