2. dma_alloc_coherent 函数原型解析

dma_alloc_coherent 是 Linux DMA 子系统中最核心的 API 之一,用于分配 一致性(Coherent)DMA 缓冲区。理解其函数原型、每个参数的含义以及返回值,是定位分配失败问题的第一步。

2.1 函数原型

#include <linux/dma-mapping.h>

void *dma_alloc_coherent(struct device *dev, size_t size,
                         dma_addr_t *dma_handle, gfp_t gfp,
                         unsigned long attrs);

2.2 参数详解

参数 类型 方向 说明
dev struct device * 输入 执行 DMA 操作的设备指针。用于关联 IOMMU/IOMMU 域、确定内存属性和 NUMA 节点。
size size_t 输入 请求分配的缓冲区大小(字节)。底层通常向上对齐到页大小。
dma_handle dma_addr_t * 输出 返回 DMA 总线地址,供设备 DMA 引擎直接使用。
gfp gfp_t 输入 内存分配标志,控制分配行为(如是否允许睡眠、是否高优先级)。
attrs unsigned long 输入 DMA 属性标志,用于附加分配约束(如不进行 Cache 刷写、指定内存属性)。

2.3 参数深度解析

2.3.1 dev — 设备指针

  • 必须有效:传入 NULL 或未初始化的设备指针会导致内核崩溃或分配失败。
  • 决定 DMA 域:设备关联的 IOMMU 决定了 dma_handle 的地址空间范围。
  • NUMA 亲和性:分配的内存优先靠近 dev 所在的 NUMA 节点,提升访问性能。
  • 调试信息:分配失败时,dev 的名称会出现在内核日志中,帮助定位。

2.3.2 size — 缓冲区大小

  • 单位:字节,但底层分配器按 页(PAGE_SIZE) 对齐。
  • 常见错误:传入 0 或过大的值(超过系统可用连续内存)。
  • 对齐要求:某些 DMA 控制器要求缓冲区对齐到特定边界(如 64 字节、128 字节)。

2.3.3 dma_handle — DMA 总线地址输出

  • 关键输出:设备 DMA 引擎使用此地址,而非 CPU 虚拟地址。
  • 类型:dma_addr_t 在 32 位系统上为 u32,在 64 位系统上为 u64。
  • 检查:分配成功后必须检查 dma_handle 是否在设备支持的地址范围内。

2.3.4 gfp — 内存分配标志

标志 含义 使用场景
GFP_KERNEL 允许睡眠,可进行页面回收 进程上下文,非原子操作
GFP_ATOMIC 不允许睡眠,使用紧急内存池 中断上下文、自旋锁持有中
GFP_DMA 限制在 ZONE_DMA(通常 <16MB) 老旧设备或特定 DMA 约束
GFP_DMA32 限制在 ZONE_DMA32(通常 <4GB) 32 位 DMA 设备
__GFP_HIGHMEM 允许从高端内存分配 大缓冲区分配,但需注意映射
__GFP_NORETRY 不重试,立即返回失败 避免长时间阻塞
⚠️ 常见陷阱:在中断上下文中使用 GFP_KERNEL 会导致内核调度异常,应使用 GFP_ATOMIC。但 GFP_ATOMIC 分配成功率较低,是失败定位的常见原因。

2.3.5 attrs — DMA 属性标志

属性 含义
DMA_ATTR_WRITE_COMBINE 使用写合并(Write Combine)内存属性,提升写性能
DMA_ATTR_NON_CONSISTENT 分配非一致性内存,需手动管理 Cache 一致性
DMA_ATTR_NO_KERNEL_MAPPING 不创建内核虚拟地址映射,仅返回 DMA 地址
DMA_ATTR_SKIP_CPU_SYNC 跳过分配时的 Cache 同步操作
DMA_ATTR_FORCE_CONTIGUOUS 强制分配物理连续内存

2.4 返回值含义

返回值 含义 后续操作
非 NULL 分配成功,返回 CPU 虚拟地址 使用 dma_handle 配置 DMA 引擎
NULL 分配失败 检查日志、内存碎片、GFP 标志、设备状态
💡 关键点:返回的 CPU 虚拟地址与 dma_handle 指向同一物理内存,但 CPU 和 DMA 引擎通过各自的地址访问。一致性内存保证双方看到的视图一致,无需手动 Cache 刷新。

2.5 函数调用流程与内存映射关系

下图展示了 dma_alloc_coherent 从调用到返回的完整流程,以及 CPU 虚拟地址、物理地址、DMA 总线地址三者之间的映射关系:

dma_alloc_coherent 调用流程与地址映射 dma_alloc_coherent() 参数校验:dev / size / gfp 选择分配路径(IOMMU / 直接) 物理内存分配(页分配器) 建立映射(页表 / IOMMU) 返回 CPU 地址 + DMA 地址 CPU 虚拟地址 ← 物理地址 → DMA 总线地址 三者指向同一物理内存,视图一致 分配失败常见原因 • 内存碎片(无连续大块) • GFP 标志不匹配上下文 • 设备 DMA 掩码限制 • IOMMU 映射空间耗尽 • 内存耗尽(OOM)

2.6 典型使用模式

dma_addr_t dma_handle;
void *cpu_addr;
size_t buf_size = 4096;  // 4KB

cpu_addr = dma_alloc_coherent(dev, buf_size, &dma_handle,
                              GFP_KERNEL, 0);
if (!cpu_addr) {
    dev_err(dev, "dma_alloc_coherent failed for size %zu\n",
            buf_size);
    return -ENOMEM;
}

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

// 释放
dma_free_coherent(dev, buf_size, cpu_addr, dma_handle);
🔧 调试建议:分配失败时,首先检查 dmesg 中是否有 coherent allocation failed 或 page allocation failure 信息,这些日志会包含 GFP 标志、请求大小和当前内存状态,是定位问题的第一手资料。