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 总线地址三者之间的映射关系:
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 标志、请求大小和当前内存状态,是定位问题的第一手资料。