4、DMA传输模式:块传输模式、分散-聚集传输模式、循环DMA模式

在PCIe驱动开发中,DMA传输模式的选择直接影响数据传输的吞吐量、CPU占用率和硬件利用率。根据数据在内存中的布局和传输触发方式,DMA传输主要分为三种模式:块传输模式、分散-聚集传输模式和循环DMA模式。理解每种模式的原理、适用场景和性能特征,是进行DMA性能调优的基础。

4.1 块传输模式(Block DMA)

块传输模式是最基础的DMA传输方式,要求源地址和目标地址在物理内存中连续。驱动在初始化时分配一块连续的DMA缓冲区,将数据一次性或分批次传输。

核心特征:物理地址连续、单次传输完成中断、适合小数据量或硬件要求严格的场景。

4.1.1 工作原理

  1. 驱动分配物理连续的DMA缓冲区(使用 dma_alloc_coherent 或 __get_free_pages)。
  2. 将缓冲区物理地址写入PCIe设备的DMA描述符寄存器。
  3. 启动DMA传输,设备从源地址读取数据写入目标地址。
  4. 传输完成后设备发出中断,驱动在中断处理中回收缓冲区或发起下一次传输。

4.1.2 性能特点

指标 说明
传输延迟 低,无需额外描述符解析
CPU占用 中等,每次传输完成需中断处理
内存碎片 高,连续大块内存分配困难
适用场景 控制类消息、小包数据、硬件约束场景
调优注意:块传输模式在长时间运行的高吞吐场景下,容易因连续内存耗尽导致分配失败。建议在驱动初始化时预分配DMA缓冲池,避免运行时动态分配。
块传输模式(Block DMA)结构图 主机内存 DMA缓冲区(物理连续) 数据块 A PCIe设备 DMA引擎 描述符寄存器 PCIe总线

4.2 分散-聚集传输模式(Scatter-Gather DMA)

分散-聚集(SG)DMA模式解决了块传输模式对物理连续性的依赖。它允许数据在物理上不连续的内存区域之间传输,通过描述符链表(Descriptor Chain)来管理多个内存片段。

核心特征:支持物理非连续内存、描述符链表管理、适合大数据量和高并发场景。

4.2.1 描述符结构

每个描述符通常包含以下字段:

字段 位宽 说明
源地址 64位 数据源物理地址
目标地址 64位 数据目标物理地址
传输长度 32位 当前片段字节数
下一个描述符指针 64位 指向链表下一个描述符
控制标志 16位 中断使能、链结束等

4.2.2 传输流程

  1. 驱动将分散的内存片段构建成描述符链表。
  2. 将链表头指针写入设备的SG描述符基址寄存器。
  3. 设备自动遍历链表,依次传输每个片段。
  4. 所有片段传输完成后,设备发出中断。
性能调优技巧:描述符链表应尽量在驱动初始化时预分配,避免运行时频繁分配/释放。描述符数量建议控制在 64~256 之间,过多会导致硬件遍历开销增大。
分散-聚集传输模式(Scatter-Gather DMA)结构图 主机内存(非连续) 片段1 片段2 片段3 描述符链表 描述符1 → 片段1 addr=0x1000, len=1024 描述符2 → 片段2 addr=0x5000, len=2048 描述符3 → 片段3 addr=0xA000, len=512

4.3 循环DMA模式(Ring/Circular DMA)

循环DMA模式是一种高效的批量传输模式,驱动和硬件共享一个环形的描述符队列。描述符在环中循环使用,驱动负责填充空闲描述符,硬件负责消费已提交的描述符,两者通过头尾指针同步。

核心特征:环形队列、头尾指针同步、减少中断频率、适合持续高吞吐场景。

4.3.1 环形队列结构

组件 说明
生产者指针(写指针) 驱动写入新描述符后更新
消费者指针(读指针) 硬件完成传输后更新
描述符槽位 固定数量的描述符,循环使用
中断节制 可配置在特定条件触发中断

4.3.2 工作流程

  1. 驱动在初始化时分配环形描述符队列,大小通常为 2 的幂次(如 256、512)。
  2. 驱动将环基址和大小写入设备寄存器。
  3. 驱动填充空闲描述符,更新生产者指针。
  4. 硬件自动轮询生产者指针,发现新描述符后执行DMA传输。
  5. 传输完成后硬件更新消费者指针,可选触发中断。
  6. 驱动在中断或轮询中回收已完成描述符。
调优注意:环形队列大小需根据吞吐量和延迟要求权衡。队列过小容易导致驱动等待空闲描述符;队列过大则增加内存占用和描述符遍历延迟。建议从 256 开始测试,根据实际负载调整。
循环DMA模式(Ring DMA)结构图 环形描述符队列 槽0 槽1 槽2 槽3 槽4 槽5 槽6 槽7 生产者 消费者 循环方向 驱动(生产者) 硬件(消费者)

4.4 三种模式对比与选型

对比维度 块传输模式 分散-聚集模式 循环DMA模式
内存连续性要求 物理连续 不要求连续 不要求连续
描述符管理 无 链表 环形队列
中断频率 每次传输 每次传输 可配置(批量中断)
CPU占用率 中 中高 低
吞吐量 低 中 高
实现复杂度 低 中 高
典型应用 控制消息、小包 网络、存储 高速数据采集、GPU
选型建议:
  • 如果硬件不支持SG或环模式,或传输数据量很小(< 4KB),优先使用块传输模式。
  • 如果数据量较大且内存碎片严重,使用分散-聚集模式。
  • 如果追求极致吞吐量且硬件支持,使用循环DMA模式,并配合中断节制(Interrupt Coalescing)进一步降低CPU开销。

4.5 代码示例:SG描述符初始化

/* SG描述符结构体定义 */
struct sg_dma_desc {
    __le64 src_addr;      /* 源物理地址 */
    __le64 dst_addr;      /* 目标物理地址 */
    __le32 length;        /* 传输长度 */
    __le16 flags;         /* 控制标志 */
    __le16 next_desc;     /* 下一个描述符索引 */
} __attribute__((packed));

/* 初始化SG描述符链表 */
int sg_dma_init(struct pci_dev *pdev, struct sg_dma_ring *ring, int num_desc)
{
    dma_addr_t dma_handle;
    struct sg_dma_desc *desc;
    int i;

    /* 分配连续内存存放描述符数组 */
    ring->desc = dma_alloc_coherent(&pdev->dev,
                    num_desc * sizeof(struct sg_dma_desc),
                    &dma_handle, GFP_KERNEL);
    if (!ring->desc)
        return -ENOMEM;

    ring->dma_handle = dma_handle;
    ring->num_desc = num_desc;
    ring->head = 0;
    ring->tail = 0;

    /* 初始化每个描述符,建立链表 */
    for (i = 0; i < num_desc; i++) {
        desc = &ring->desc[i];
        memset(desc, 0, sizeof(*desc));
        desc->next_desc = (i + 1) % num_desc;  /* 循环链表 */
        desc->flags = 0;
    }

    /* 将描述符基址写入设备寄存器 */
    pci_write_config_dword(pdev, SG_DESC_BASE_LOW,
                           lower_32_bits(dma_handle));
    pci_write_config_dword(pdev, SG_DESC_BASE_HIGH,
                           upper_32_bits(dma_handle));
    pci_write_config_dword(pdev, SG_DESC_COUNT, num_desc);

    return 0;
}
关键点:
  • 描述符使用 dma_alloc_coherent 分配,保证DMA可访问。
  • 链表采用循环结构,最后一个描述符指向第一个。
  • 描述符基址和数量需写入硬件寄存器,硬件才能正确遍历。