DMA 传输类型:Block DMA、Scatter-Gather DMA、Ring Buffer
做 PCIe 驱动面试,DMA 传输类型几乎是必考题。面试官会问你:“你们项目用的哪种 DMA?选型依据是什么?” 说白了,这块就是看你有没有真正调过硬件,踩过坑。
我个人把常见 DMA 传输方式分为三类:Block DMA、Scatter-Gather DMA、Ring Buffer DMA。每种都有自己的脾气,选错了后面能把你折腾死。
1. Block DMA(块传输)
Block DMA 是最简单粗暴的方式。一次传输要求物理内存是连续的一大块。DMA 引擎拿到起始地址和长度就开始搬,搬完中断通知 CPU。
我遇到过一类踩坑案例:客户在嵌入式系统里做视频采集,每次数据包 8KB,用 Block DMA 跑得好好的。后来升级系统,内核开了大量设备驱动,内存频繁申请释放,连续页申请成功率暴跌。DMA 请求排队等着分配连续内存,帧率直接掉到 10 fps。
Block DMA 的好处是硬件实现简单,中断次数少。坏处就不说了——内存连续性门槛太高,大块传输基本不可靠。
2. Scatter-Gather DMA(散聚传输)
Scatter-Gather DMA(简称 SG DMA)是现代 PCIe 设备的主流方案。它允许 DMA 引擎把多个不连续的物理内存块打包成一个传输任务。怎么做?靠一张 SG 表(Scatter-Gather List)——每个表项记录一块物理地址和长度,DMA 引擎按表项顺序挨个搬。
我习惯把 SG 表看作“快递配送清单”:CPU 告诉 DMA 去哪些地址取货、送到哪些地址,DMA 自己按清单跑一趟。这样 CPU 就不用等每次传输完才干下一件事。
SG 表结构(典型格式)
struct sg_entry {
uint64_t dma_addr; // 源或目的物理地址
uint32_t length; // 本次传输字节数
uint32_t flags; // 控制位(如最后一项标记)
};
DMA 引擎遍历这张表,直到遇到 “last” 标记。整个过程可以批量提交,减少 CPU 干预。项目中我常用 SG DMA 配合 PCIe MRd/MWr 事务,带宽能跑到线速的 95% 以上。
老实说,SG DMA 也不是没坑。驱动必须管理 SG 表的生命周期——表项必须驻留在内存中,且对 DMA 引擎可见(不能 cache 污染)。我曾经在某个 SOC 平台上遇到 SG 表放在 cacheable 区域,DMA 读到的是旧数据,一传就出错。最后加了 dma_sync_single_for_device 才解决。
3. Ring Buffer DMA(环形缓冲区传输)
Ring Buffer 是另一个流派。它在主存里开辟一个固定大小的环形队列(通常叫描述符环),驱动往里写描述符、硬件从另一端取描述符去执行,完成后再把状态写回描述符。CPU 和硬件通过写指针(P)和消费指针(C)协同工作。
| 概念 | 用途 |
|---|---|
| Producer Index | 驱动写入新描述符后更新,通知硬件有活干 |
| Consumer Index | 硬件处理完描述符后更新,通知驱动 DMA 完成 |
| Ring Size | 描述符个数,通常是 2 的幂(方便取模) |
Ring Buffer 的最大优势是无锁设计。驱动只需更新一个写指针,硬件只读另一个指针,完全不用互斥。在高速网络、NVMe、GPU 场景里,环形缓冲区几乎是标配。
我记得第一次调 Ring Buffer 时犯了个蠢错:环大小设成了 127,而不是 128。结果生产者索引回卷时取模计算错误,描述符永远写不对位置。这类问题但凡驱动写多了,都能第一时间怀疑到环大小必须是 2 的幂上。
4. 三种 DMA 传输类型对比
| 项目 | Block DMA | SG DMA | Ring Buffer DMA |
|---|---|---|---|
| 内存连续性 | 必须连续 | 支持不连续 | 支持(描述符连续即可) |
| 描述符管理 | 无(只有地址+长度) | SG 表(可链式) | 环形队列 |
| CPU 干预频率 | 每次传输都中断 | 批量触发中断 | 按水位或超时中断 |
| 典型应用 | SPI、I2C、小数据采集 | 存储、网络、GPU、视频 | NVMe、万兆网卡、专业加速卡 |
| 驱动复杂度 | 简单 | 中等(需维护 SG 表) | 中等(需管理环指针) |
| 带宽利用率 | 低(CPU 频繁干预) | 高 | 极高(流式处理) |
5. 实际项目怎么选?
如果是面试题,很多候选人背得出定义,但问到“为什么选 Ring Buffer 而不选 SG”就卡壳。我一般在项目中按以下逻辑判断:
- 数据流方向固定、描述符数量可预测 → 优先 Ring Buffer。比如 NVMe 的 SQ/CQ 就是天然环形。
- 数据流随机、描述符大小不定 → 用 SG DMA。比如显卡的显存映射,物理页不连续但要批量传输。
- 简单小系统,一个中断处理一次搬移 → Block DMA 最简单,不用折腾。
不过也别一根筋。有些硬件同时支持 SG 和 Ring Buffer的低级模式(Legacy Ring),描述符格式长得像 SG 表但通过环指针管理。碰到这种混血设备,驱动要小心——我曾经在某个 FPGA 核上把 SG 表当 Ring Buffer 用,环指针回卷时表项没更新,结果 DMA 一直在搬旧数据,排查了三天。
嗯,这三种类型本质上就是在资源(内存连续性)、延迟(中断开销)、吞吐(批量传输)三者间做权衡。没有银弹,只有最合适的。