2. DMA 基础原理:DMA 控制器架构、DMA 传输流程
DMA(Direct Memory Access,直接存储器访问)是 Linux 驱动性能优化的核心硬件机制。它允许外设与内存之间直接交换数据,而无需 CPU 逐字节搬运。理解 DMA 控制器的内部架构和传输流程,是后续进行驱动性能调优和 CPU 负载均衡的基石。
2.1 DMA 控制器架构
一个典型的 DMA 控制器(DMAC)内部由多个功能模块组成,负责管理总线仲裁、地址生成和传输控制。下图展示了一个支持多通道的通用 DMA 控制器架构。
DMA 控制器的主要组件及其职责如下表所示:
| 组件 | 职责描述 | 性能关键点 |
|---|---|---|
| DMA 引擎/状态机 | 控制传输状态机,协调地址、计数和总线请求 | 状态切换延迟直接影响传输启动速度 |
| 通道寄存器组 | 每个通道独立保存源地址、目的地址、传输计数、控制字 | 寄存器深度影响链式传输能力 |
| 总线仲裁器 | 管理多个通道对系统总线的竞争访问 | 仲裁策略(固定优先级/轮询)影响实时性 |
| 地址发生器 | 支持地址递增、递减、固定模式,处理地址对齐 | 地址对齐能力影响传输效率 |
| 中断控制器 | 传输完成、错误等事件产生中断信号 | 中断合并能力影响 CPU 负载 |
2.2 DMA 传输流程
一次完整的 DMA 传输经历四个阶段:请求、响应、传输、中断。理解每个阶段的硬件行为和软件交互点,是驱动开发中正确配置 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_*() 系列函数在传输完成后解除映射。