4. DMA 与内存访问:DMA 引擎原理、DMA 映射与一致性、Cache 一致性对性能的影响
在嵌入式 Linux 系统中,数据搬运是驱动最频繁的操作之一。直接内存访问(DMA)允许外设与内存直接交换数据,无需 CPU 逐字节干预,是降低延迟、提升吞吐量的核心技术。然而,DMA 与 CPU 共享内存时,Cache 一致性问题会成为性能瓶颈甚至数据错误的根源。本章深入剖析 DMA 引擎原理、映射机制以及 Cache 一致性对性能的影响。
4.1 DMA 引擎原理与传输模型
DMA 引擎是一个专用的硬件模块,负责在内存与外设之间、或内存与内存之间搬运数据。CPU 只需配置传输描述符(Descriptor),然后启动 DMA,即可在传输期间执行其他任务。
核心性能指标: DMA 传输延迟通常为微秒级,吞吐量可达数 Gbps,远优于 PIO(Programmed I/O)模式。在高速外设(如 GigE、NVMe、Camera)中,DMA 是必备能力。
关键概念: 描述符环(Descriptor Ring)是 DMA 引擎与驱动之间的通信队列。驱动将传输请求填入环,DMA 引擎按序取出并执行,完成后通过中断或轮询通知驱动。
4.2 DMA 映射与一致性
DMA 操作的内存区域必须是物理连续的,且总线地址与 CPU 虚拟地址可能不同。Linux 内核提供 DMA 映射 API 来管理这些转换,并保证数据一致性。
4.2.1 流式映射 vs. 一致性映射
| 特性 | 流式映射 (Streaming DMA) | 一致性映射 (Coherent DMA) |
|---|---|---|
| 分配方式 | dma_map_single() / dma_map_sg() | dma_alloc_coherent() |
| Cache 一致性 | 需手动维护(flush/invalidate) | 硬件保证一致性 |
| 适用场景 | 短时、频繁的传输(网络包、块 I/O) | 长期共享缓冲区(控制结构、帧缓冲) |
| 性能开销 | 低(无额外映射开销) | 中等(可能占用 TLB 或使用特殊内存区域) |
| 典型延迟 | ~1-5 µs(含 cache 操作) | ~0.5-2 µs(无 cache 操作) |
常见错误: 使用流式映射后忘记调用 dma_unmap_single() 会导致 DMA 地址泄漏;在 DMA 传输进行中修改缓冲区内容而不做 cache 同步,会导致数据不一致。
4.2.2 流式映射的 Cache 同步操作
流式映射要求驱动在以下时机显式执行 cache 操作:
- 传输前(CPU→DMA): 调用 dma_sync_single_for_device() 将 CPU 写入的数据 flush 到内存。
- 传输后(DMA→CPU): 调用 dma_sync_single_for_cpu() 使 cache 行失效,确保 CPU 读到的是 DMA 写入的最新数据。
/* 发送数据示例:CPU 准备数据,DMA 读取 */
void tx_dma(struct device *dev, void *buf, size_t len) {
dma_addr_t dma_handle;
dma_handle = dma_map_single(dev, buf, len, DMA_TO_DEVICE);
if (dma_mapping_error(dev, dma_handle)) {
dev_err(dev, "DMA mapping failed\n");
return;
}
/* 确保 CPU 写入已刷出 cache */
dma_sync_single_for_device(dev, dma_handle, len, DMA_TO_DEVICE);
/* 启动 DMA 传输 */
start_dma(dma_handle, len);
/* 传输完成后 */
dma_unmap_single(dev, dma_handle, len, DMA_TO_DEVICE);
}
/* 接收数据示例:DMA 写入,CPU 读取 */
void rx_dma(struct device *dev, void *buf, size_t len) {
dma_addr_t dma_handle;
dma_handle = dma_map_single(dev, buf, len, DMA_FROM_DEVICE);
if (dma_mapping_error(dev, dma_handle)) {
dev_err(dev, "DMA mapping failed\n");
return;
}
/* 启动 DMA 传输 */
start_dma(dma_handle, len);
/* 等待传输完成 */
wait_for_dma_done();
/* 使 cache 失效,读取 DMA 写入的数据 */
dma_sync_single_for_cpu(dev, dma_handle, len, DMA_FROM_DEVICE);
process_data(buf, len);
dma_unmap_single(dev, dma_handle, len, DMA_FROM_DEVICE);
}
4.3 Cache 一致性对性能的影响
Cache 一致性问题是 DMA 性能优化的核心挑战。当 CPU 和 DMA 同时访问同一内存区域时,Cache 中的旧数据与内存中的新数据可能不一致,导致数据错误或性能下降。
4.3.1 一致性问题场景
4.3.2 性能影响量化
| 操作 | 延迟(典型值) | 吞吐量影响 |
|---|---|---|
| Cache 命中(CPU 读) | ~3-10 ns | 无影响 |
| Cache 未命中(CPU 读内存) | ~50-100 ns | 下降 5-10x |
| Cache flush(单行) | ~10-30 ns | 可忽略 |
| Cache flush(全片) | ~1-10 µs | 高延迟抖动 |
| DMA 传输(4KB) | ~1-5 µs | 高吞吐 |
| DMA + 不必要的 cache 操作 | 额外 +2-10 µs | 吞吐下降 20-50% |
优化原则: 尽量减少 cache 操作的粒度。对于大块 DMA 传输,使用 dma_map_sg() 批量映射,避免逐页 sync。对于控制结构等小数据,优先使用一致性映射(dma_alloc_coherent),消除 cache 维护开销。
4.3.3 避免 Cache 一致性问题的策略
- 使用一致性内存: 对于频繁共享的小缓冲区(如描述符环、状态寄存器),使用 dma_alloc_coherent() 分配非 cacheable 内存。
- 精确 sync 范围: 只 sync 实际传输的字节范围,避免全缓冲区 sync。
- 双缓冲(Double Buffering): 交替使用两个缓冲区,一个供 DMA 使用,一个供 CPU 处理,避免共享冲突。
- 硬件辅助一致性: 部分 SoC 支持硬件 snoop 或 IOMMU 一致性,可减少软件开销。
/* 使用一致性映射分配描述符环 */
struct dma_ring {
void *cpu_addr;
dma_addr_t dma_addr;
size_t size;
};
struct dma_ring *alloc_dma_ring(struct device *dev, size_t size) {
struct dma_ring *ring;
ring = kzalloc(sizeof(*ring), GFP_KERNEL);
if (!ring) return NULL;
ring->cpu_addr = dma_alloc_coherent(dev, size, &ring->dma_addr, GFP_KERNEL);
if (!ring->cpu_addr) {
kfree(ring);
return NULL;
}
ring->size = size;
return ring;
}
void free_dma_ring(struct device *dev, struct dma_ring *ring) {
if (ring) {
dma_free_coherent(dev, ring->size, ring->cpu_addr, ring->dma_addr);
kfree(ring);
}
}
4.4 性能优化实战:网络驱动中的 DMA 调优
以 GigE 网络驱动为例,DMA 配置直接影响 PPS(Packets Per Second)和延迟。
| 参数 | 默认值 | 优化值 | 效果 |
|---|---|---|---|
| 描述符环大小 | 128 | 512 | 减少丢包,提升吞吐 |
| DMA 突发长度 | 8 | 16 | 提升总线利用率 |
| 中断合并阈值 | 1 包/中断 | 4 包/中断 | 降低 CPU 占用,延迟增加 |
| Cache sync 粒度 | 全缓冲区 | 仅数据包长度 | 减少 30-50% cache 操作 |
实测数据: 在某 ARM Cortex-A72 平台上,优化 DMA 描述符环大小从 128 增至 512,配合精确 cache sync,TCP 吞吐量从 680 Mbps 提升至 940 Mbps,CPU 占用从 45% 降至 28%。
4.5 总结
- DMA 引擎通过描述符环实现高效数据搬运,CPU 仅在传输开始和结束时介入。
- 流式映射适合短时传输,需手动维护 cache 一致性;一致性映射适合长期共享缓冲区。
- Cache 一致性问题是 DMA 性能的核心瓶颈,精确 sync 和双缓冲是常用优化手段。
- 实际驱动开发中,应根据外设特性选择映射类型,并量化 cache 操作对延迟和吞吐的影响。