2. DMA 基础原理:DMA 控制器架构、DMA 传输流程

DMA(Direct Memory Access,直接存储器访问)是 Linux 驱动性能优化的核心硬件机制。它允许外设与内存之间直接交换数据,而无需 CPU 逐字节搬运。理解 DMA 控制器的内部架构和传输流程,是后续进行驱动性能调优和 CPU 负载均衡的基石。

2.1 DMA 控制器架构

一个典型的 DMA 控制器(DMAC)内部由多个功能模块组成,负责管理总线仲裁、地址生成和传输控制。下图展示了一个支持多通道的通用 DMA 控制器架构。

DMA 控制器内部架构图 DMA 控制器内部架构 DMA 引擎 / 状态机 通道 0 源地址 / 目的地址 传输计数 / 控制字 通道 1 源地址 / 目的地址 传输计数 / 控制字 通道 2 源地址 / 目的地址 传输计数 / 控制字 通道 3 源地址 / 目的地址 传输计数 / 控制字 总线仲裁器 地址发生器 中断控制器 说明: 多通道设计允许同时管理多个独立 DMA 传输;每个通道包含独立的源/目的地址寄存器和传输计数寄存器。 总线仲裁器决定哪个通道获得总线控制权,地址发生器负责递增/递减地址,中断控制器在传输完成时通知 CPU。

DMA 控制器的主要组件及其职责如下表所示:

组件 职责描述 性能关键点
DMA 引擎/状态机 控制传输状态机,协调地址、计数和总线请求 状态切换延迟直接影响传输启动速度
通道寄存器组 每个通道独立保存源地址、目的地址、传输计数、控制字 寄存器深度影响链式传输能力
总线仲裁器 管理多个通道对系统总线的竞争访问 仲裁策略(固定优先级/轮询)影响实时性
地址发生器 支持地址递增、递减、固定模式,处理地址对齐 地址对齐能力影响传输效率
中断控制器 传输完成、错误等事件产生中断信号 中断合并能力影响 CPU 负载

2.2 DMA 传输流程

一次完整的 DMA 传输经历四个阶段:请求、响应、传输、中断。理解每个阶段的硬件行为和软件交互点,是驱动开发中正确配置 DMA 的前提。

DMA 传输四阶段流程图 DMA 传输四阶段流程 阶段 1:请求 外设发出 DMA 请求 (DREQ 信号) 阶段 2:响应 CPU 释放总线控制权 (HLDA 应答) 阶段 3:传输 数据在内存与外设间 直接搬运 阶段 4:中断 传输完成 → 触发中断 CPU 处理完成回调 循环模式:自动重新开始下一轮传输 关键时序说明: 1. 请求阶段:外设(如网卡、声卡)将 DREQ 信号置为有效,通知 DMA 控制器需要传输数据。 2. 响应阶段:DMA 控制器向 CPU 发出 HOLD 请求,CPU 在完成当前总线周期后释放总线,返回 HLDA 应答。 3. 传输阶段:DMA 控制器获得总线控制权,按配置的地址和计数进行数据搬运,每传输一个单元释放一次总线(周期窃取)。

2.3 传输模式详解

DMA 控制器支持多种传输模式,驱动开发者需要根据外设特性和系统负载选择合适的模式。下表对比了三种主流传输模式:

传输模式 工作原理 总线占用 适用场景
单次传输(Single Transfer) 每次 DMA 请求只传输一个数据单元(字节/字/双字) 低,每次传输后释放总线 低速外设、对 CPU 响应要求高的系统
块传输(Block Transfer) 一次请求传输整个数据块,直到计数归零才释放总线 高,传输期间独占总线 高速外设、大数据量批量传输
链式传输(Scatter-Gather / Descriptor) 通过描述符链表管理多个不连续的内存缓冲区,自动加载下一个描述符 中等,描述符加载时有短暂总线占用 网络数据包、音视频流、零拷贝场景

性能优化提示:在 Linux 驱动中,struct dma_async_tx_descriptor 是链式传输的核心数据结构。通过 dmaengine_prep_slave_sg() 函数可以构建散列表描述符,实现多个不连续物理内存区域的连续 DMA 传输,避免 CPU 进行内存拷贝。

2.4 中断处理与 CPU 负载

DMA 传输完成后,中断控制器向 CPU 发送中断信号。中断处理效率直接影响 CPU 负载和系统吞吐量。现代 DMA 控制器支持以下中断优化机制:

  • 中断合并(Interrupt Coalescing): 多个传输完成事件合并为一个中断,减少 CPU 中断次数。
  • 中断阈值(Interrupt Threshold): 设置传输计数阈值,达到指定数量才触发中断。
  • 错误中断分离: 传输完成中断与错误中断使用不同中断号或标志位,减少 ISR 判断开销。

驱动开发要点:在注册 DMA 中断处理函数时,使用 devm_request_irq() 并设置 IRQF_TRIGGER_HIGH 或 IRQF_TRIGGER_RISING 标志。中断处理函数中应优先检查传输完成标志,快速清除中断状态,将耗时操作推迟到 tasklet 或 workqueue 中执行。

2.5 关键数据结构与 API 映射

Linux DMA 引擎子系统(dmaengine)对底层 DMA 控制器进行了抽象。驱动开发者通过标准 API 操作 DMA 传输,无需关心具体硬件寄存器细节。

硬件概念 Linux 内核抽象 关键 API / 数据结构
DMA 通道 struct dma_chan dma_request_channel()
传输描述符 struct dma_async_tx_descriptor dmaengine_prep_slave_sg()
传输配置 struct dma_slave_config dmaengine_slave_config()
传输提交 描述符提交到通道 dmaengine_submit()
传输启动 硬件开始执行 dma_async_issue_pending()
传输完成回调 描述符回调函数 desc->callback

常见陷阱:DMA 传输涉及物理地址,必须使用 dma_map_single() 或 dma_map_sg() 进行地址映射和一致性处理。在传输完成前,CPU 不能访问已映射的缓冲区,否则会导致缓存一致性问题(Cache Coherency)。使用 dma_unmap_*() 系列函数在传输完成后解除映射。