4、DMA传输模式:块传输模式、分散-聚集传输模式、循环DMA模式
在PCIe驱动开发中,DMA传输模式的选择直接影响数据传输的吞吐量、CPU占用率和硬件利用率。根据数据在内存中的布局和传输触发方式,DMA传输主要分为三种模式:块传输模式、分散-聚集传输模式和循环DMA模式。理解每种模式的原理、适用场景和性能特征,是进行DMA性能调优的基础。
4.1 块传输模式(Block DMA)
块传输模式是最基础的DMA传输方式,要求源地址和目标地址在物理内存中连续。驱动在初始化时分配一块连续的DMA缓冲区,将数据一次性或分批次传输。
核心特征:物理地址连续、单次传输完成中断、适合小数据量或硬件要求严格的场景。
4.1.1 工作原理
- 驱动分配物理连续的DMA缓冲区(使用
dma_alloc_coherent或__get_free_pages)。 - 将缓冲区物理地址写入PCIe设备的DMA描述符寄存器。
- 启动DMA传输,设备从源地址读取数据写入目标地址。
- 传输完成后设备发出中断,驱动在中断处理中回收缓冲区或发起下一次传输。
4.1.2 性能特点
| 指标 | 说明 |
|---|---|
| 传输延迟 | 低,无需额外描述符解析 |
| CPU占用 | 中等,每次传输完成需中断处理 |
| 内存碎片 | 高,连续大块内存分配困难 |
| 适用场景 | 控制类消息、小包数据、硬件约束场景 |
调优注意:块传输模式在长时间运行的高吞吐场景下,容易因连续内存耗尽导致分配失败。建议在驱动初始化时预分配DMA缓冲池,避免运行时动态分配。
4.2 分散-聚集传输模式(Scatter-Gather DMA)
分散-聚集(SG)DMA模式解决了块传输模式对物理连续性的依赖。它允许数据在物理上不连续的内存区域之间传输,通过描述符链表(Descriptor Chain)来管理多个内存片段。
核心特征:支持物理非连续内存、描述符链表管理、适合大数据量和高并发场景。
4.2.1 描述符结构
每个描述符通常包含以下字段:
| 字段 | 位宽 | 说明 |
|---|---|---|
| 源地址 | 64位 | 数据源物理地址 |
| 目标地址 | 64位 | 数据目标物理地址 |
| 传输长度 | 32位 | 当前片段字节数 |
| 下一个描述符指针 | 64位 | 指向链表下一个描述符 |
| 控制标志 | 16位 | 中断使能、链结束等 |
4.2.2 传输流程
- 驱动将分散的内存片段构建成描述符链表。
- 将链表头指针写入设备的SG描述符基址寄存器。
- 设备自动遍历链表,依次传输每个片段。
- 所有片段传输完成后,设备发出中断。
性能调优技巧:描述符链表应尽量在驱动初始化时预分配,避免运行时频繁分配/释放。描述符数量建议控制在 64~256 之间,过多会导致硬件遍历开销增大。
4.3 循环DMA模式(Ring/Circular DMA)
循环DMA模式是一种高效的批量传输模式,驱动和硬件共享一个环形的描述符队列。描述符在环中循环使用,驱动负责填充空闲描述符,硬件负责消费已提交的描述符,两者通过头尾指针同步。
核心特征:环形队列、头尾指针同步、减少中断频率、适合持续高吞吐场景。
4.3.1 环形队列结构
| 组件 | 说明 |
|---|---|
| 生产者指针(写指针) | 驱动写入新描述符后更新 |
| 消费者指针(读指针) | 硬件完成传输后更新 |
| 描述符槽位 | 固定数量的描述符,循环使用 |
| 中断节制 | 可配置在特定条件触发中断 |
4.3.2 工作流程
- 驱动在初始化时分配环形描述符队列,大小通常为 2 的幂次(如 256、512)。
- 驱动将环基址和大小写入设备寄存器。
- 驱动填充空闲描述符,更新生产者指针。
- 硬件自动轮询生产者指针,发现新描述符后执行DMA传输。
- 传输完成后硬件更新消费者指针,可选触发中断。
- 驱动在中断或轮询中回收已完成描述符。
调优注意:环形队列大小需根据吞吐量和延迟要求权衡。队列过小容易导致驱动等待空闲描述符;队列过大则增加内存占用和描述符遍历延迟。建议从 256 开始测试,根据实际负载调整。
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可访问。 - 链表采用循环结构,最后一个描述符指向第一个。
- 描述符基址和数量需写入硬件寄存器,硬件才能正确遍历。