1. 课程导入
在嵌入式、服务器、存储与网络设备中,CPU 与外部设备之间的数据搬运量往往远超纯计算量。如果每一次数据搬运都由 CPU 逐字节读写,系统吞吐会被迅速拖垮。DMA(Direct Memory Access,直接内存访问)正是为解决这一问题而生的硬件机制:它让外设在不占用 CPU 指令周期的情况下,直接与系统内存交换数据。但 DMA 带来高性能的同时,也引入了 Linux 内核中最容易出错、最难调试的一类问题——缓存一致性。
本课程的目标不是让你背下 API,而是让你建立一套可落地的工程判断力:看到一段 DMA 代码,能判断它是否存在缓存一致性风险;遇到数据错乱、偶发丢包、校验失败,能沿着 DMA 与缓存的信号链定位根因;在性能优化时,知道该用流式映射还是相干映射,该在何处插入屏障与同步。
一、核心概念:从一次数据搬运说起
要理解 DMA 与缓存一致性,先要理解三个角色的关系:CPU、Cache、DMA 控制器。CPU 访问内存时,数据会先进入 Cache;而 DMA 控制器通常绕过 CPU,直接访问物理内存(DRAM)。这就产生了一个经典矛盾:CPU 看到的是 Cache 中的副本,DMA 看到的是 DRAM 中的原始数据,两者可能不一致。
- DMA 地址:设备使用的总线地址,需经 IOMMU 或直接映射转换为物理地址。
- 一致性映射(Coherent Mapping):如
dma_alloc_coherent(),分配的内存保证 CPU 与设备看到相同数据,适合长期共享的控制结构。 - 流式映射(Streaming Mapping):如
dma_map_single(),用于一次性传输,需要显式同步,性能更高但更易出错。 - Cache 维护操作:clean(写回)、invalidate(失效)、flush(两者兼有),是软件保证一致性的关键手段。
- 内存屏障:
dma_wmb()、dma_rmb()保证描述符写入顺序对设备可见。
二、整体知识框架:一张图看清全貌
下图给出本课程的知识骨架:从硬件信号链出发,经过 Linux DMA API 层,落到缓存一致性策略与性能优化手段,最终形成可复用的实战路线。图中同时标出了 CPU、Cache、DMA 控制器与内存之间的数据路径,以及一致性维护的插入点。
三、方法流程:一次安全 DMA 传输的标准动作
无论使用哪种 DMA 框架,一次安全的流式 DMA 传输都应遵循固定顺序。下面以字符设备发送缓冲区为例,给出典型流程与关键代码骨架。
/* 1. 分配并填充缓冲区(CPU 写) */
buf = kmalloc(len, GFP_KERNEL);
memcpy(buf, src, len);
/* 2. 流式映射,方向为 TO_DEVICE */
dma_addr = dma_map_single(dev, buf, len, DMA_TO_DEVICE);
if (dma_mapping_error(dev, dma_addr)) { /* 错误处理 */ }
/* 3. 确保 CPU 写入对设备可见(clean cache) */
dma_sync_single_for_device(dev, dma_addr, len, DMA_TO_DEVICE);
/* 4. 启动 DMA 传输,等待完成中断 */
start_dma(dma_addr, len);
/* 5. 传输完成后解除映射 */
dma_unmap_single(dev, dma_addr, len, DMA_TO_DEVICE);
接收方向则相反:先映射为 DMA_FROM_DEVICE,DMA 完成后调用 dma_sync_single_for_cpu() 使 CPU 看到设备写入的新数据,再读取缓冲区。顺序错误是导致“偶发读到旧数据”的首要原因。
四、典型场景与误区复盘
考虑一个网卡收包场景:驱动为每个描述符分配 2KB 缓冲区,DMA 写入后 CPU 读取协议头。若在 DMA 完成中断中忘记调用 dma_sync_single_for_cpu(),CPU 可能从 Cache 中读到上一次的旧包内容,表现为“随机丢包”或“协议解析错误”。这类问题在 x86 上可能因 Cache 行为不同而难以复现,但在 ARM 平台上极易暴露。
| 常见误区 | 现象 | 正确做法 |
|---|---|---|
| 映射后不调用 sync | 设备读到旧数据或 CPU 读到旧数据 | 按方向调用 for_device / for_cpu |
| 缓冲区未按 Cache line 对齐 | 相邻数据被意外 clean/invalidate | 使用对齐分配或填充至 cache line 边界 |
| 在中断中频繁 map/unmap | CPU 占用高,吞吐下降 | 预分配相干缓冲区或复用映射 |
| 忽略 dma_mapping_error | IOMMU 映射失败后访问非法地址 | 每次映射后检查返回值 |
dma-debug 与 ftrace 观察真实的映射与同步行为,比只读代码更有效。
接下来的章节将沿着“硬件信号链 → DMA API → 一致性策略 → 性能优化 → 实战排障”这条主线逐步展开。你将学会用 dma_alloc_coherent() 构建共享环形缓冲区,用 dma_sync_* 精确控制同步点,用 dma-debug 捕获映射泄漏,并最终能独立分析一个真实驱动中的 DMA 性能瓶颈与一致性缺陷。