1. DMA一致性内存分配概述
1.1 dma_alloc_coherent 的核心作用
dma_alloc_coherent 是 Linux 内核中用于分配 DMA 一致性内存(Coherent DMA Memory)的核心 API。它保证分配的内存区域在 CPU 和 DMA 控制器(如网卡、存储控制器、USB 主机控制器)之间保持 缓存一致性,无需软件手动刷新缓存或执行内存屏障。
关键特性: 调用 dma_alloc_coherent 返回的虚拟地址和物理地址,CPU 与 DMA 设备对该内存的访问在任何时刻都是一致的,不会出现脏缓存行导致的数据不一致问题。
函数原型如下:
void *dma_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t flag);
| 参数 | 说明 |
|---|---|
dev |
请求 DMA 的设备指针,用于关联设备与内存区域 |
size |
分配的内存大小(字节),必须为页对齐的倍数 |
dma_handle |
输出参数,返回给 DMA 控制器使用的总线地址 |
flag |
内存分配标志,如 GFP_KERNEL、GFP_ATOMIC |
| 返回值 | CPU 可访问的虚拟地址;失败返回 NULL |
1.2 与流式 DMA 映射的区别
流式 DMA 映射(Streaming DMA Mapping)通过 dma_map_single / dma_unmap_single 管理,两者在缓存处理策略上存在本质差异:
| 对比维度 | 一致性 DMA(dma_alloc_coherent) | 流式 DMA(dma_map_single) |
|---|---|---|
| 缓存策略 | 禁用 CPU 缓存或使用非缓存映射 | 使用缓存,通过 flush/invalidate 维护一致性 |
| 分配时机 | 驱动初始化时一次性分配 | 每次 DMA 传输前映射,传输后解映射 |
| 生命周期 | 长期持有,直到驱动卸载 | 短时持有,传输完成后立即释放 |
| API 开销 | 分配/释放开销大,但使用中无额外开销 | 每次传输需 map/unmap,开销较小 |
| 适用场景 | 控制结构、描述符环、状态寄存器 | 大数据块传输(网络包、文件数据) |
| 错误恢复 | 分配失败需回滚整个设备初始化 | 映射失败可重试或使用备用缓冲区 |
经验法则: 对于频繁访问且数据量小的控制结构(如描述符环、DMA 命令队列),优先使用一致性 DMA;对于大块数据流传输,使用流式 DMA 可获得更好的缓存利用率和性能。
1.3 一致性内存的特性
一致性内存具备以下关键特性,这些特性直接决定了其分配失败时的调试方向:
- 物理连续性: 分配的内存区域在物理地址空间上是连续的,这是 DMA 控制器寻址的基本要求。
- 非缓存映射: CPU 页表将该区域标记为不可缓存(或使用特殊缓存策略),避免 CPU 缓存与 DMA 写入之间的数据不一致。
- 对齐约束: 分配的内存通常以页(4KB)为边界对齐,部分硬件要求更大的对齐(如 64KB、1MB)。
- 低地址限制: 某些 32 位 DMA 设备只能访问 32 位地址空间,需要从 ZONE_DMA 区域分配。
- 不可迁移: 一致性内存一旦分配,物理页框被锁定,不能进行内存规整或迁移。
1.4 分配失败的影响范围
当 dma_alloc_coherent 返回 NULL 时,整个设备驱动初始化流程通常无法继续。典型的影响包括:
- 设备无法启动: 无法分配描述符环或命令队列,设备无法接收任何 I/O 请求。
- 系统可用性下降: 对于存储控制器或网络接口,分配失败可能导致文件系统挂载失败或网络服务不可用。
- 内存碎片化加剧: 长时间运行的系统中,物理内存碎片化是分配失败的主要原因之一。
- 调试困难: 失败原因可能是内存不足、区域限制、对齐约束或 IOMMU 配置错误,需要系统化排查。
注意: 在嵌入式系统中,ZONE_DMA 区域通常只有 16MB 或 64MB,频繁分配大块一致性内存极易耗尽该区域。分配前应评估设备对地址范围的限制,必要时使用
dma_set_mask 放宽地址约束。