2. Linux DMA API 概述:核心 API 介绍

在 PCIe 驱动开发中,DMA(直接内存访问)是设备与系统内存之间进行高速数据传输的核心机制。Linux 内核提供了一套标准化的 DMA API,用于管理设备与内存之间的数据一致性。本章将详细介绍三个最核心的 DMA API:dma_alloc_coherent、dma_map_single 和 dma_map_sg,并阐述它们的使用场景与差异。

2.1 DMA API 分类:一致性映射 vs 流式映射

Linux DMA API 从映射管理方式上分为两大类:一致性映射(Coherent Mapping)和流式映射(Streaming Mapping)。两者的核心区别在于对 Cache 一致性的处理策略不同。

特性 一致性映射(Coherent) 流式映射(Streaming)
Cache 一致性 硬件/软件保证始终一致 需要驱动在访问前后显式同步
内存分配方式 专用分配(dma_alloc_coherent) 映射已有内存(dma_map_single/sg)
适用场景 长期共享的控制结构、描述符环 短时、大批量数据缓冲区
性能开销 较高(可能禁用 Cache 或使用特殊内存) 较低(利用 Cache,减少同步次数)
API 典型函数 dma_alloc_coherent / dma_free_coherent dma_map_single / dma_unmap_single / dma_map_sg
选择原则:对于驱动与设备之间频繁访问、生命周期长的数据结构(如 DMA 描述符环、状态寄存器),优先使用一致性映射;对于一次性的数据块传输(如网络包、块设备 I/O),优先使用流式映射以获得更好的 Cache 利用率和性能。
DMA API 分类与映射关系 DMA API 分类与映射关系 Linux DMA API 一致性映射 (Coherent) 流式映射 (Streaming) dma_alloc_coherent dma_free_coherent dma_map_single / dma_unmap_single dma_map_sg / dma_unmap_sg 选择依据:数据结构生命周期 × 访问频率 × 一致性开销 控制结构 → 一致性映射;数据缓冲区 → 流式映射

2.2 dma_alloc_coherent:一致性内存分配

dma_alloc_coherent 用于分配一段在 CPU 和设备视角下始终保持 Cache 一致性的 DMA 缓冲区。该函数返回两个地址:CPU 虚拟地址(供驱动访问)和 DMA 总线地址(供设备访问)。

// 函数原型
void *dma_alloc_coherent(struct device *dev, size_t size,
                         dma_addr_t *dma_handle, gfp_t flag);

// 典型用法示例
struct device *dev = &pdev->dev;
dma_addr_t dma_handle;
void *cpu_addr;
size_t size = 4096;  // 分配 4KB 一致性缓冲区

cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);
if (!cpu_addr) {
    dev_err(dev, "Failed to allocate coherent DMA memory\n");
    return -ENOMEM;
}

// 使用 cpu_addr 和 dma_handle 进行 DMA 操作
// ...

// 释放一致性内存
dma_free_coherent(dev, size, cpu_addr, dma_handle);
关键参数说明:
  • dev:指向 struct device 的指针,用于标识 DMA 所属设备。
  • size:分配的字节数,通常需要对齐到页大小。
  • dma_handle:输出参数,返回设备可用的 DMA 总线地址。
  • flag:内存分配标志,常用 GFP_KERNEL(进程上下文)或 GFP_ATOMIC(中断上下文)。
注意事项:一致性映射的内存通常来自特殊内存区域(如 CMA 或 IOMMU 保留区),分配速度较慢且可能浪费 Cache 性能。不要在热路径上频繁分配/释放一致性内存,建议在驱动初始化时一次性分配,在卸载时释放。

2.3 dma_map_single:流式映射单个缓冲区

dma_map_single 用于将一段已有的内核内存映射为 DMA 可访问的流式缓冲区。映射后,驱动必须调用 dma_sync_single_for_cpu 或 dma_sync_single_for_device 来维护 Cache 一致性。

// 函数原型
dma_addr_t dma_map_single(struct device *dev, void *cpu_addr,
                          size_t size, enum dma_data_direction dir);

// 典型用法示例
void *buffer = kmalloc(2048, GFP_KERNEL);
if (!buffer)
    return -ENOMEM;

// 将内核缓冲区映射为 DMA 地址
dma_addr_t dma_addr = dma_map_single(dev, buffer, 2048, DMA_TO_DEVICE);
if (dma_mapping_error(dev, dma_addr)) {
    dev_err(dev, "dma_map_single failed\n");
    kfree(buffer);
    return -ENOMEM;
}

// 启动 DMA 传输(设备从 buffer 读取数据)
// ...

// 传输完成后,解除映射
dma_unmap_single(dev, dma_addr, 2048, DMA_TO_DEVICE);
kfree(buffer);
方向标志 含义 典型场景
DMA_TO_DEVICE 数据从内存传输到设备 发送网络包、写入块设备
DMA_FROM_DEVICE 数据从设备传输到内存 接收网络包、读取块设备
DMA_BIDIRECTIONAL 双向数据传输 帧缓冲、共享状态区
DMA_NONE 调试用途,无实际传输 初始化或错误路径
性能提示:流式映射不会复制数据,仅修改页表或 Cache 策略。映射开销远小于一致性分配,适合高频 I/O 路径。但必须严格遵循“映射 → 传输 → 同步 → 访问 → 解映射”的生命周期。

2.4 dma_map_sg:流式映射 Scatter/Gather 列表

当 DMA 传输涉及多个物理上不连续的缓冲区时,使用 dma_map_sg 可以将一个 struct scatterlist 数组映射为设备可访问的 DMA 地址列表。该函数会返回映射成功的段数,并填充每个 scatterlist 的 dma_address 和 dma_length 字段。

// 函数原型
int dma_map_sg(struct device *dev, struct scatterlist *sg,
               int nents, enum dma_data_direction dir);

// 典型用法示例
struct scatterlist sg[3];
int i, nents = 3, mapped;

// 初始化 scatterlist(假设已有三个缓冲区)
sg_init_table(sg, nents);
sg_set_buf(&sg[0], buf0, len0);
sg_set_buf(&sg[1], buf1, len1);
sg_set_buf(&sg[2], buf2, len2);

// 映射整个 SG 列表
mapped = dma_map_sg(dev, sg, nents, DMA_TO_DEVICE);
if (mapped == 0) {
    dev_err(dev, "dma_map_sg failed\n");
    return -ENOMEM;
}

// 遍历映射后的 SG 列表,获取 DMA 地址和长度
for_each_sg(sg, s, mapped, i) {
    dma_addr_t addr = sg_dma_address(s);
    unsigned int len = sg_dma_len(s);
    // 将 addr 和 len 写入设备描述符
}

// 传输完成后解除映射
dma_unmap_sg(dev, sg, nents, DMA_TO_DEVICE);
dma_map_sg 映射流程 dma_map_sg 映射流程 物理缓冲区 1 (4KB) 物理缓冲区 2 (8KB) 物理缓冲区 3 (2KB) sg[0] → buf0, len0 sg[1] → buf1, len1 sg[2] → buf2, len2 DMA 地址 A (4KB) DMA 地址 B (8KB) DMA 地址 C (2KB) dma_map_sg 将分散的物理缓冲区映射为连续的 DMA 地址列表 设备通过描述符链依次访问每个 DMA 段,实现 Scatter/Gather 传输

2.5 核心 API 对比总结

API 映射类型 内存来源 Cache 一致性 典型用途
dma_alloc_coherent 一致性映射 专用分配 始终一致 描述符环、控制结构
dma_map_single 流式映射 已有内核内存 需显式同步 单缓冲区 I/O
dma_map_sg 流式映射 已有内核内存 需显式同步 Scatter/Gather 传输
实践建议:
  • 初始化阶段分配一致性内存,运行时使用流式映射进行数据传输。
  • 流式映射必须配对使用 dma_map_* 和 dma_unmap_*,避免资源泄漏。
  • 使用 dma_mapping_error 检查映射是否成功,尤其是在 IOMMU 使能的系统上。
  • 对于高性能设备,优先使用 dma_map_sg 减少 DMA 描述符数量,提高传输效率。