1. DMA 概述:为什么我们需要它?
1.1 DMA 是什么?
DMA,全称 Direct Memory Access,直接存储器访问。说白了,就是一种不需要 CPU 插手的数据搬运机制。
我刚开始接触嵌入式开发时,总觉得 CPU 才是老大,什么事情都得它亲自干。后来做视频采集卡的项目,发现 200 MHz 的 ARM 核全速运行也就勉强跟上 1080p 的帧率——CPU 根本没空处理业务逻辑了。那时候才明白:数据的终极敌人是 CPU 的“事必躬亲”。
DMA 的本质是:让硬件控制器直接接管内存外设之间的数据通道。CPU 只需要告诉 DMA 控制器:“从地址 A 搬 N 个字节到地址 B,搬完了通知我一声。”剩下的,不用管了。
1.2 DMA 的核心作用
我习惯把 DMA 的作用归纳为三点,你品品:
- 解放 CPU:CPU 从 1 字节 1 字节的拷贝中解脱出来,去做真正需要它决策的事——比如跑算法、处理中断、管理任务。
- 提升吞吐量:DMA 控制器通常独立于 CPU 核心,可以全速搬数据。我记得在 PCIe Gen3 x4 的项目里,DMA 跑满 3.9 GB/s 带宽时,CPU 占用率不到 5%。
- 降低延迟:特别是对于周期性、批量数据传输,DMA 能保证确定性延迟,不会因为 CPU 调度抖动。
1.3 基本工作原理:谁动了我的数据?
DMA 的工作流程不复杂,你想象一下快递运输:
- 配置:CPU 设置 DMA 控制器的源地址、目的地址、传输长度、传输方向(内存→外设 / 外设→内存 / 内存→内存)。
- 启动:CPU 写一个控制位(比如 DMA 通道使能),DMA 开始工作。
- 搬运:DMA 控制器从源地址读取数据,写入目的地址;每完成一个单元(字节/字/突发),源地址和目的地址自动增减,计数器递减。
- 完成:当计数器归零,DMA 控制器向 CPU 发送中断(或者通过事件链触发其他 DMA 通道)。
举个例子,UART 接收:当 UART 收到一个字节,它会拉起一个 DMA 请求信号。DMA 控制器响应该请求,把 UART 接收寄存器的值写入内存缓冲区。整个过程 CPU 完全不知道——直到缓冲区满了 DMA 发出中断。
1.4 历史上的那些事儿
DMA 概念其实很早就有了。我记得第一个广泛使用的 DMA 控制器是 Intel 8237,1980 年代就在 PC/XT 上出现了。那会儿的 DMA 很简陋:只支持 8 位传输,最大寻址 16 MB,传输模式只有单字节和块传输。
90 年代 PCI 总线带来了总线主控 DMA,外设可以直接成为总线主设备发起传输,不再需要专门的 DMA 控制器。这算是 DMA 的一次革命。
如今,现代 SoC 里的 DMA 控制器已经很复杂:
- 多个独立通道(16/32 路)
- 支持链式描述符(硬件自动处理下一段传输)
- 支持分散/聚集(Scatter-Gather)——把物理上不连续的内存块串起来传输
- 支持数据宽度自动调整
- 集成在高速接口内部(比如 PCIe DMA、USB DMA、Ethernet DMA)
1.5 本章知识图谱
下图帮你梳理本章的核心逻辑:DMA 不是什么黑魔法,它就是一套配置→启动→搬运→完成的硬件机制。
1.6 一个小表格:DMA vs CPU 搬运
很多人问我:既然 DMA 这么好,为什么有时候还是用 CPU 拷贝?我通常用这个对比表格说明:
| 场景 | DMA 适合 | CPU 适合 |
|---|---|---|
| 数据量 | 大块数据(≥ 1 KB) | 小块数据(≤ 64 B) |
| 传输频率 | 持续高速流 | 零星、突发 |
| 实时性要求 | 确定性延迟 | 允许 jitter |
| CPU 负载 | 极低 | 占用 CPU 100% |
| 地址对齐 | 对对齐有要求(否则性能下降) | 任意地址 |
好了,关于 DMA 的概述就先讲这么多。记住一句话:DMA 是高速接口的灵魂。后面章节我们会逐个深挖它在 PCIe、USB、Ethernet、DDR 等等场景下的具体玩法。