1. DMA技术概述——让CPU从数据搬运中解放出来
各位同学,今天开始我跟大家聊DMA。DMA,全称Direct Memory Access,直接存储器访问。
说白了,就是硬件自己搬数据,不用CPU一句一句指令去挪。你想想看,要是每次从网卡收一个包、从ADC读一个采样,CPU都得停下手里的事去搬运——那CPU还能干正事儿吗?
1.1 DMA原理——一个“硬件搬运工”
DMA的核心思想很简单:在系统总线上挂一个专用控制器(DMA Controller),它负责在外设和存储器之间,或者存储器和存储器之间,直接搬数据。搬运期间,CPU可以继续做运算、跑逻辑,两不耽误。
我早年做一款工业相机项目时,图像数据每帧2MB,如果让CPU逐像素搬运,帧率直接砍半。后来换上DMA,CPU只负责配置和中断处理,吞吐量立刻翻倍。嗯,那个坑踩得值。
DMA工作三要素
- 源地址:数据从哪来。例如外设FIFO、内存地址X。
- 目的地址:数据搬到哪去。例如内存缓冲区地址Y。
- 传输长度:搬多少个字节/字。通常由CPU提前配置。
一次典型的DMA传输流程就像自助快递代收——CPU告诉DMA:“从A点取100个包裹,放到B点”,DMA自己搞定路线和交接,然后给CPU回一个“好了”的信号。
| 维度 | CPU直接搬运 | DMA搬运 |
|---|---|---|
| 开销 | 每条指令都在消耗总线周期 | 只消耗配置和中断处理周期 |
| CPU利用率 | 搬运期间CPU被占满 | 搬运期间CPU可以跑其他任务 |
| 传输速度 | 受CPU时钟频率限制 | 由总线带宽决定,通常更高 |
| 代码复杂度 | 简单循环 | 需配置DMA寄存器+中断服务 |
1.2 DMA与CPU交互——如何“礼貌”地共享总线
DMA不是独立存在的,它必须和CPU配合。它们共享同一套系统总线(如AXI4、AHB),所以谁先走、谁让路,必须有个约定。
交互流程概括
- 配置阶段:CPU写DMA控制器的寄存器——源地址、目的地址、长度、传输模式(单次/循环)等。
- 请求阶段:外设(比如UART接收到了数据)向DMA发送DMA请求信号(DREQ)。
- 仲裁阶段:DMA向总线仲裁器申请总线控制权(BUSREQ)。仲裁器根据优先级决定把总线交给DMA还是CPU。
- 传输阶段:DMA获得总线后,直接发起读/写事务,数据在外设FIFO和内存之间流动。
- 结束阶段:传输完成后,DMA向CPU发中断(或者轮询状态位)。CPU知道数据已经就绪,可以处理了。
这里有个细节容易栽跟头:总线仲裁的优先级。我曾经在一个多DMA的系统里,两个外设同时请求,结果低优先级的外设一直吃不到总线,数据溢出丢包。后来我加了一个轮询仲裁模式,才解决问题。
1.3 DMA应用场景——三种经典搬运模式
DMA常见的三种传输方向,我来逐个拆解一下。
| 传输方向 | 英文简称 | 典型例子 | CPU介入程度 |
|---|---|---|---|
| 外设 → 内存 | Peripheral-to-Memory | ADC采样数据搬到DDR | 只配置和响应中断 |
| 内存 → 外设 | Memory-to-Peripheral | SPI发送缓冲区数据 | 只配置和触发一次 |
| 内存 → 内存 | Memory-to-Memory | 两块DDR区域间拷贝 | 搬运期间几乎不占用CPU |
外设到内存(Peripheral-to-Memory)
这个我最常用。比如FPGA采集的视频数据,通过AXI DMA直接流到DDR。CPU只需要在帧完成中断后读帧头即可。我在一个高速数据采集卡项目中,用这种方式实现了200MB/s的持续写入,CPU占用率不到5%。爽。
内存到外设(Memory-to-Peripheral)
比如I2S音频播放。CPU把PCM音频数据准备好放在DDR里,然后触发DMA,DMA把数据喂给I2S TX FIFO。CPU可以同时处理UI,不会因为喂数据导致卡顿。
内存到内存(Memory-to-Memory)
这个模式在Xilinx DMA配置里稍微有点特殊——需要设置源和目的都是内存地址。常用于图像数据拷贝、协议包重组。不过老实说,除非你真的需要解放CPU去干别的事,否则用memcpy就够了。DMA的配置延迟有时候反而不如Cache优化后的memcpy快。嗯,这个问题我们到后面“性能优化”那章再细聊。
上面这张图就是DMA系统的主干。你可以看到,DMA控制器是CPU的“数据搬运替身”,它与外设、内存直接打交道,而CPU只需通过总线仲裁器和中断来协调。
我觉得你如果能理解这张图的流向,DMA的原理就算掌握一半了。剩下的一半,是我们后面要花大量篇幅讲的——如何用Xilinx的DMA IP核在FPGA里把这些节点搭起来、打通数据通道。