1. DMA 基础概念:工作原理、CPU 交互与 Linux 内核角色

1.1 什么是 DMA?

直接内存访问(Direct Memory Access,DMA) 是一种允许硬件设备(如网卡、磁盘控制器、GPU)直接读写系统内存,而无需 CPU 介入每个数据传输周期的技术。在高速 I/O 场景下,DMA 是降低 CPU 负载、提升系统吞吐量的核心机制。

核心收益: 一次 DMA 传输仅需 CPU 在启动时和完成时参与,中间的数据搬运完全由 DMA 控制器(DMAC)硬件完成。相比 PIO(Programmed I/O)模式,CPU 利用率可提升 10 倍以上。

1.2 DMA 工作原理

DMA 传输的基本流程包含三个阶段:请求、搬运、完成通知。下图展示了典型的 DMA 工作流程:

DMA 工作原理流程图 DMA 传输三阶段流程 ① CPU 配置 DMAC 源地址、目的地址、长度 ② DMA 搬运数据 设备 ↔ 内存,CPU 并行 ③ DMA 完成中断 通知 CPU 传输结束 CPU 在阶段②可执行其他任务 实现 CPU 与 I/O 并行 外设 (网卡/磁盘) 系统内存 (DRAM) DMA 数据通道

1.3 DMA 与 CPU 的交互模式

DMA 与 CPU 的交互主要分为三种模式,实际系统中常根据设备特性选择:

交互模式 描述 CPU 负载 典型场景
轮询模式 CPU 循环检查 DMA 状态寄存器 高(持续占用) 低延迟、短传输(如 SPI)
中断驱动模式 DMA 完成时触发中断,CPU 响应处理 中(仅完成时) 块设备、网络包收发
描述符链模式 DMA 自动加载下一个描述符,批量传输 低(批量完成中断) 高速网卡、NVMe SSD
实时系统建议: 中断驱动模式是 Linux 内核默认的 DMA 交互方式。在低延迟场景下,需配合中断亲和性(IRQ Affinity)将 DMA 中断绑定到指定 CPU 核心,避免中断在多个核心间迁移导致缓存抖动。

1.4 DMA 在 Linux 内核中的角色

Linux 内核通过 通用 DMA API 为驱动开发者提供统一的 DMA 操作接口,屏蔽了不同架构(x86、ARM、RISC-V)的硬件差异。核心角色包括:

  • DMA 映射(Mapping): 将内核虚拟地址转换为设备可访问的物理地址或 IOMMU 地址。
  • 一致性内存(Coherent Memory): 为 DMA 分配 CPU 和设备都能直接访问的共享内存区域。
  • 流式映射(Streaming Mapping): 针对一次性的 DMA 传输,动态建立和销毁映射关系。
  • 中断处理(Interrupt Handling): 将 DMA 完成中断路由到指定 CPU,配合 NAPI 实现高效收包。

下图展示了 Linux 内核中 DMA 子系统的软件架构层次:

Linux 内核 DMA 软件架构 Linux 内核 DMA 子系统架构 设备驱动层 (Driver) 网卡、NVMe、GPU 驱动 通用 DMA API 层 dma_alloc_coherent / dma_map_single 架构相关实现层 x86 SWIOTLB / ARM IOMMU / RISC-V 关键组件 • dma_ops 操作集 • IOMMU 页表管理 • SWIOTLB 反弹缓冲 • 一致性内存池 • 中断亲和性设置 • NAPI 轮询机制 硬件层 (Hardware) DMA 控制器 / IOMMU / 总线桥

1.5 关键数据结构

Linux 内核使用 struct device 中的 dma_ops 指针来管理 DMA 操作。以下是核心数据结构关系:

// include/linux/dma-mapping.h (简化示意)
struct dma_map_ops {
    void*   (*alloc)(struct device *dev, size_t size,
                     dma_addr_t *dma_handle, gfp_t flag,
                     unsigned long attrs);
    void    (*free)(struct device *dev, size_t size,
                    void *vaddr, dma_addr_t dma_handle,
                    unsigned long attrs);
    dma_addr_t (*map_page)(struct device *dev, struct page *page,
                           unsigned long offset, size_t size,
                           enum dma_data_direction dir,
                           unsigned long attrs);
    void    (*unmap_page)(struct device *dev, dma_addr_t dma_handle,
                          size_t size, enum dma_data_direction dir,
                          unsigned long attrs);
    int     (*dma_supported)(struct device *dev, u64 mask);
};

// 每个设备通过 dev->dma_ops 获取对应架构的实现
struct device {
    ...
    const struct dma_map_ops *dma_ops;
    u64         *dma_mask;      // 设备 DMA 地址范围
    ...
};
常见误区: 初学者常误以为 DMA 传输不需要 CPU 参与。实际上,DMA 的启动配置和完成处理仍然需要 CPU 执行指令。在实时系统中,DMA 中断的响应延迟直接影响整体确定性,这也是本课程后续章节重点优化的方向。

1.6 本章小结

  • DMA 通过硬件控制器直接搬运数据,释放 CPU 用于计算任务。
  • Linux 内核通过通用 DMA API 实现跨架构兼容,驱动开发者无需关心底层细节。
  • 中断驱动模式是主流交互方式,中断亲和性设置是实时优化的起点。
  • 理解 dma_ops 和 struct device 的关系是深入内核 DMA 子系统的关键。