3. DMA 基础回顾:DMA 控制器工作原理、传输模式与地址映射

在深入缓存一致性协议之前,必须扎实掌握 DMA 控制器的底层行为。DMA(Direct Memory Access)允许外设与内存直接交换数据,无需 CPU 逐字节搬运,从而大幅提升系统吞吐量。然而,DMA 的“旁路”特性也正是缓存一致性问题的根源——DMA 直接读写物理内存,而 CPU 可能还在 Cache 中持有旧副本。

3.1 DMA 控制器工作原理

DMA 控制器本质上是一个专用的总线主控引擎。它从 CPU 接管总线控制权,在源地址与目的地址之间搬运数据,搬运完成后通过中断通知 CPU。典型的工作流程包含以下步骤:

  1. CPU 配置 DMA 寄存器:设置源地址、目的地址、传输长度、传输模式、触发源等参数。
  2. CPU 启动 DMA:写 DMA 使能位或触发软件请求。
  3. DMA 请求总线:DMA 控制器向总线仲裁器发出总线请求(Bus Request)。
  4. 总线授权:仲裁器在 CPU 空闲或总线空闲时授予总线控制权。
  5. DMA 执行传输:按配置的传输模式(Burst/Single)在源与目的之间搬运数据。
  6. 传输完成:DMA 释放总线,向 CPU 发送中断信号。
关键认知: DMA 传输过程中,CPU 可以继续执行指令(如果指令和数据都在 Cache 中命中),但 CPU 无法访问被 DMA 占用的总线。DMA 操作的是物理地址,完全绕过 CPU 的 Cache 层次。
排查提示: 在调试 DMA 相关问题时,首先确认 DMA 寄存器配置是否正确,特别是地址对齐和传输长度。错误的地址对齐可能导致 Burst 传输退化为 Single 传输,性能骤降。
DMA 控制器工作原理流程图 DMA 控制器工作原理流程图 CPU 配置 DMA 寄存器 CPU 启动 DMA DMA 请求总线 总线仲裁授权 DMA 执行数据传输 传输完成 → 触发中断 CPU 可继续执行指令 (Cache 命中时)

3.2 传输模式:Burst 与 Single

DMA 传输模式直接决定总线占用时长和传输效率。两种模式的核心差异在于:一次总线仲裁后连续传输的数据量。

特性 Burst(突发)模式 Single(单次)模式
一次仲裁传输量 连续传输多个数据单元(如 4/8/16 次) 每次仲裁只传输 1 个数据单元
总线占用时间 长,持续占用总线直到 Burst 完成 短,每次传输后释放总线
传输效率 高(减少仲裁开销) 低(频繁仲裁)
对 CPU 影响 CPU 可能长时间无法访问总线 CPU 有更多机会抢占总线
典型应用 大块数据搬运(如帧缓冲、文件系统) 小数据量、实时性要求高的场景
缓存一致性风险 高(连续修改内存,Cache 行易失效) 低(单次修改,CPU 可及时同步)
实战避坑: Burst 传输中,如果 DMA 修改的内存区域恰好与 CPU Cache 行重叠,且 Cache 策略为写回(Write-Back),则 DMA 完成后 CPU 读到的可能是 Cache 中的脏数据。这是缓存一致性问题的典型触发场景。
Burst 与 Single 传输模式对比 Burst 与 Single 传输模式对比 Burst 模式 一次仲裁 → 连续传输 4/8/16 次 总线持续占用,CPU 等待 Single 模式 每次仲裁 → 只传输 1 次 频繁仲裁,CPU 可抢占 选择 Burst 还是 Single 取决于: 数据块大小 / 实时性要求 / 总线负载 / Cache 策略 Burst 长度越大,缓存一致性风险越高

3.3 地址映射:DMA 眼中的内存世界

DMA 控制器直接操作物理地址空间,不经过 MMU 的虚拟地址转换。这意味着:

  • DMA 地址 = 物理地址:配置 DMA 时填入的地址必须是物理地址(PA),而非 CPU 看到的虚拟地址(VA)。
  • 连续物理内存:DMA 要求传输缓冲区在物理地址上连续。如果使用不连续的物理页,需要借助 SG(Scatter-Gather)链表。
  • IOMMU/SMMU 介入:在虚拟化或安全系统中,IOMMU 可以为 DMA 提供设备虚拟地址(DVA)到物理地址的映射,但底层仍然是物理地址访问。
地址映射核心原则: CPU 通过 Cache 访问内存(虚拟地址 → 物理地址),DMA 直接访问物理内存。两者看到的“内存视图”不同,这是缓存一致性问题的根本原因。
地址类型 使用者 转换方式 是否经过 Cache
虚拟地址(VA) CPU 指令/数据访问 MMU 页表转换 → PA 是(Cache 命中时)
物理地址(PA) DMA 控制器、Cache 标签 直接使用 否(DMA 直接访问内存控制器)
设备虚拟地址(DVA) 支持 IOMMU 的 DMA IOMMU 页表转换 → PA 否(DMA 路径)
DMA 与 CPU 地址映射对比 DMA 与 CPU 地址映射对比 CPU 访问路径 CPU 虚拟地址 MMU 页表转换 Cache 查找 物理内存 DMA 访问路径 DMA 物理地址 总线矩阵 物理内存 关键差异:DMA 绕过 Cache 直接访问物理内存 CPU 通过 Cache 看到的是“缓存副本”,DMA 看到的是“真实内存” 当 DMA 修改内存后,CPU Cache 中的对应行变为“陈旧”

3.4 地址映射实战:Scatter-Gather 与一致性

当 DMA 缓冲区需要跨越多个不连续的物理页时,必须使用 Scatter-Gather(SG)链表。SG 链表由多个描述符组成,每个描述符包含一个物理地址段和长度。DMA 控制器自动遍历链表,完成整块数据的搬运。

SG 与一致性: 使用 SG 时,每个描述符指向的物理页可能被 CPU 缓存。如果 DMA 写入了某个页,而 CPU 的 Cache 中恰好有该页的脏数据,则一致性被破坏。因此,在启动 DMA 前,必须对涉及的所有物理页执行 Cache 刷新(Clean)或失效(Invalidate)操作。
/* 伪代码:DMA SG 描述符初始化与 Cache 同步 */
struct sg_descriptor {
    uint32_t phys_addr;  /* 物理地址 */
    uint32_t length;     /* 传输长度 */
    uint32_t flags;      /* 控制标志 */
    uint32_t next;       /* 下一个描述符指针 */
};

void dma_prepare_sg(struct sg_descriptor *sg_list, int num_entries) {
    for (int i = 0; i < num_entries; i++) {
        /* 1. 对每个物理页执行 Cache 刷新(写方向)或失效(读方向) */
        cache_flush_invalidate_page(sg_list[i].phys_addr, sg_list[i].length);
        
        /* 2. 确保描述符本身也在一致性内存中 */
        dma_sync_single_for_device(sg_list[i].phys_addr, sg_list[i].length, DMA_TO_DEVICE);
    }
    /* 3. 写 DMA 寄存器,启动传输 */
    dma_start(sg_list);
}
常见错误: 工程师容易忘记在 DMA 完成后再次执行 Cache 失效操作。DMA 写入内存后,CPU 必须失效对应 Cache 行,才能读到最新数据。否则,CPU 可能读到 Cache 中的旧值,导致数据错误。