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 引擎按序取出并执行,完成后通过中断或轮询通知驱动。
DMA 引擎传输模型 CPU DMA 引擎 内存 外设 描述符环 (描述符 0..N) 配置描述符 启动 读/写数据 数据流 取描述符 完成中断 CPU 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 一致性问题场景

Cache 一致性问题场景 CPU Cache (旧数据) DMA 引擎 内存 (新数据) 读旧数据 写新数据 不一致! CPU 读路径 DMA 写路径 不一致点

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 一致性问题的策略

  1. 使用一致性内存: 对于频繁共享的小缓冲区(如描述符环、状态寄存器),使用 dma_alloc_coherent() 分配非 cacheable 内存。
  2. 精确 sync 范围: 只 sync 实际传输的字节范围,避免全缓冲区 sync。
  3. 双缓冲(Double Buffering): 交替使用两个缓冲区,一个供 DMA 使用,一个供 CPU 处理,避免共享冲突。
  4. 硬件辅助一致性: 部分 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 操作对延迟和吞吐的影响。