DMA 基础概念
1.1 什么是 DMA
DMA 的全称是 Direct Memory Access,中文叫直接内存访问。说白了,就是让一块硬件独立完成内存和设备之间的数据搬运,CPU 站在旁边看着就行。
我刚开始做嵌入式的时候,以为外设传输数据都得 CPU 亲力亲为——读一个字节,写到内存,再读一个字节……效率极低。后来知道有 DMA 这个“搬运工”,才恍然大悟。
打个比方:CPU 是项目经理,DMA 就是仓库里的叉车。项目经理想搬十箱货,不用自己动手,喊一声“叉车去搬”,然后就可以干别的活了。叉车自己搞定从哪里搬、搬多少、放哪里,搬完了按喇叭通知一下。
我的个人经验: 第一次用 DMA 时,我还在寄存器里手动配源地址、目的地址、长度,然后等中断。后来才发现,现代 DMA 控制器都支持描述符链表,一次配好几十个任务,CPU 根本不用管中间过程。
1.2 DMA 的工作原理
DMA 的核心是一个专用的 DMA 控制器(简称 DMAC)。它内部有几个关键寄存器:
- 源地址寄存器 – 从哪读数据
- 目的地址寄存器 – 写到哪去
- 传输计数寄存器 – 搬多少个字节/字/块
- 控制寄存器 – 传输方向、宽度、突发模式、中断使能等
工作流程大致是这样:
- CPU 配置 DMA 控制器(设置源、目的、长度、模式)。
- 外设(比如 UART、SDIO)向 DMA 发出请求。
- DMA 向总线仲裁器申请总线控制权。
- 获得总线后,DMA 直接读外设数据,写到内存(或反之)。
- 每传完一个单元,计数减一。计数到零时,DMA 产生中断给 CPU。
- 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 的工作流串联起来了:
这张图里箭头标出了配置、请求、读写、中断的完整链条。你可以看到,CPU 只在开始和结束时参与,中间的数据搬移全由 DMA 自己跑。
嗯,到这里 DMA 的基础概念就差不多了。想深入的话,下一步就是看寄存器手册和内核的 dmaengine 框架,不过那是后面章节的事。你只要记住这几点:DMA 是硬件搬砖工;配好源、目的、长度就让它去跑;注意 cache 一致性;描述符链是高端玩法。