3、DMA传输模式详解:内存到内存、内存到外设、外设到内存、循环模式(Scatter-Gather)

DMA(Direct Memory Access)传输模式是决定数据搬运效率与系统耦合度的核心设计维度。不同的传输模式适用于不同的硬件拓扑与业务场景。本章将深入剖析四种基础 DMA 传输模式:内存到内存、内存到外设、外设到内存,以及高级的 Scatter-Gather(循环/分散聚合)模式,并结合网络驱动中的数据加速场景进行工程化解读。

3.1 内存到内存(Memory-to-Memory)

内存到内存模式是最基础的 DMA 传输形式,通常用于在系统内存的不同区域之间高速复制数据块,无需 CPU 逐字节搬运。

适用场景:帧缓冲拷贝、加密/压缩中间缓冲区交换、用户态与内核态数据镜像。

传输流程

  1. CPU 配置 DMA 控制器:源地址(内存区域 A)、目的地址(内存区域 B)、传输长度。
  2. DMA 控制器向总线仲裁器请求总线控制权。
  3. 获得控制权后,DMA 直接从源地址读取数据,写入目的地址。
  4. 传输完成,DMA 控制器触发中断通知 CPU。
内存到内存 DMA 传输流程 CPU DMA 控制器 内存区域 A 内存区域 B 配置描述符 读取数据 写入数据 完成中断
工程注意:内存到内存 DMA 在部分 SoC(如某些 ARM 平台)中可能不支持或性能低于预期,因为需要占用内存总线带宽,反而可能影响 CPU 对内存的访问延迟。使用前务必查阅芯片参考手册。

3.2 内存到外设(Memory-to-Device)

内存到外设模式是网络驱动中最常见的 DMA 方向之一。CPU 将待发送的数据包组织在内存中的 DMA 缓冲区,DMA 控制器负责将数据从内存搬运到网络接口控制器(NIC)的 FIFO 或内部缓冲区。

关键特征

属性说明
数据流向系统内存 → 外设(如网卡 TX FIFO)
触发方式CPU 写外设控制寄存器,或外设通过 DMA 请求信号发起
典型应用网卡发送数据包、GPU 上传纹理、USB 批量输出
同步机制完成中断或轮询状态寄存器
内存到外设 DMA 传输(网卡发送方向) 系统内存 TX 描述符环 DMA 引擎 总线主控 NIC 网卡 TX FIFO DMA 读取 DMA 写入 TX 完成中断
性能优化点:在内存到外设传输中,应确保 DMA 缓冲区在物理地址上连续(或使用 IOMMU/SMMU 映射),避免外设无法访问跨页内存。同时,合理设置 TX 描述符环深度(如 256/512)可平衡延迟与吞吐量。

3.3 外设到内存(Device-to-Memory)

外设到内存模式是网络接收路径的核心。网卡接收到数据帧后,通过 DMA 直接将数据写入系统内存中预先分配的缓冲区,CPU 仅在收到中断后处理已就绪的数据。

与 PIO 模式对比

对比项DMA 模式PIO 模式(Programmed I/O)
数据搬运者DMA 控制器(硬件)CPU(逐字读取)
CPU 占用率极低(仅中断处理)高(持续占用)
吞吐量高(可达到总线极限)低(受限于 CPU 速度)
延迟较低(硬件并行)较高(CPU 串行)
适用场景高速网络(1G/10G/25G+)低速设备或调试阶段
外设到内存 DMA 传输(网卡接收方向) NIC 网卡 RX FIFO DMA 引擎 总线主控 系统内存 RX 描述符环 DMA 读取 DMA 写入 RX 完成中断
工程实践:在 Linux 网络驱动中,外设到内存 DMA 通常与 NAPI(New API)配合使用。网卡在中断中触发 NAPI 调度,轮询模式下批量处理多个接收包,减少中断次数,提升吞吐量。

3.4 循环模式与 Scatter-Gather(分散聚合)

Scatter-Gather 是 DMA 的高级传输模式,允许一次 DMA 操作访问多个非连续的内存缓冲区。它通过一个描述符链表(Descriptor Chain)来定义多个源/目的地址块,DMA 控制器自动遍历链表完成所有传输。

核心概念

  • Scatter(分散):从多个不连续的内存源地址读取数据,聚合后发送到外设。
  • Gather(聚合):从外设接收数据,分散写入多个不连续的内存目的地址。
  • 描述符环(Descriptor Ring):循环缓冲区,每个描述符包含地址、长度、控制标志和指向下一个描述符的指针。

描述符结构示例

struct dma_desc {
    u32     addr;       // 缓冲区物理地址
    u32     length;     // 传输长度
    u32     flags;      // 控制标志(中断使能、链结束等)
    u32     next_desc;  // 下一个描述符指针(物理地址)
};
Scatter-Gather DMA 描述符环结构 描述符 0 addr=0x1000, len=256 描述符 1 addr=0x2000, len=512 描述符 2 addr=0x3000, len=128 描述符 N addr=0x4000, len=64 next_ptr next_ptr next_ptr 循环回到描述符 0(环形) Scatter-Gather 优势 • 无需物理连续大块内存 • 支持零拷贝:skb 数据直接 映射到描述符缓冲区 • 一次 DMA 请求传输多个 不连续数据块 • 减少中断次数,提升吞吐量 • 硬件自动遍历描述符链表 • 适用于 TCP 分段卸载(TSO) 和接收侧缩放(RSS)

网络驱动中的 Scatter-Gather 应用

在 Linux 网络驱动中,Scatter-Gather 与 sk_buff 结构深度绑定。一个网络数据包可能分散在多个内存页中(如 TCP 分段、协议头与 payload 分离),通过 Scatter-Gather DMA,驱动可以直接将这些分散的页地址填入描述符,无需先拷贝到连续缓冲区。

关键 API:在 Linux 内核中,dma_map_sg() 和 dma_unmap_sg() 用于将分散/聚合列表(SG list)映射到 DMA 地址空间。驱动通过 sg_dma_address() 和 sg_dma_len() 获取映射后的地址和长度,填入硬件描述符。

四种模式对比总结

模式数据流向内存连续性要求典型硬件支持网络驱动应用
内存到内存内存 → 内存源/目的连续通用 DMA 控制器帧缓冲拷贝、协议栈内部交换
内存到外设内存 → 外设源连续网卡 TX 引擎数据包发送
外设到内存外设 → 内存目的连续网卡 RX 引擎数据包接收
Scatter-Gather双向(多块)无需连续高级网卡(Intel/ Mellanox/ Broadcom)零拷贝、TSO、LRO、大包收发
避坑指南:Scatter-Gather 虽然强大,但描述符环的深度和缓冲区大小需要精心配置。过浅的描述符环会导致 DMA 引擎空转等待;过深则浪费内存。建议根据网络带宽和 MTU 计算:描述符深度 = 带宽 × 延迟 / 平均包长。例如 10Gbps 网络、100μs 延迟、1500 字节包长,深度约需 833,通常取 1024。