1. 课程导入

在嵌入式与服务器系统中,CPU 与内存之间的数据搬运长期占据大量时钟周期。当外设需要持续吞吐数据时,如果每一次字节搬移都由 CPU 亲自执行,处理器就会被"搬运工"角色拖住,无法专注于计算与调度。DMA(Direct Memory Access,直接内存访问)正是为解决这一矛盾而诞生的硬件机制:它让外设与内存之间建立一条不经过 CPU 核心的通路,由独立的 DMA 控制器完成地址递增、数据搬运和中断上报。理解 DMA 的演进脉络,是掌握 Linux dmaengine 框架与后续性能调优的起点。

一、DMA 技术演进:从单通道到通用引擎

早期 DMA 控制器结构简单,通常只有固定数量的通道,每个通道绑定一个外设,寄存器直接暴露给驱动,配置方式高度依赖具体芯片。这种"一芯一驱动"的模式在 SoC 外设数量激增后迅速暴露问题:驱动代码重复、跨平台移植困难、通道资源无法动态复用。

随后出现的分散/聚集(Scatter-Gather,SG)能力,使一次 DMA 事务可以跨越多个不连续的内存页,显著提升了大块数据传输效率。再往后,链式描述符、循环缓冲、双缓冲等机制被引入,DMA 开始支持音频、网络、存储等持续流式场景。到了现代 SoC,DMA 控制器往往具备多通道、多优先级、可编程突发长度、硬件握手接口等特性,逐渐演化为一个可被多种外设共享的"通用数据搬运引擎"。

这一演进带来的核心变化是:DMA 不再只是某个外设的附属品,而是系统级资源。如何统一管理这些资源、屏蔽硬件差异、向上层提供稳定接口,成为 Linux 内核必须解决的问题,dmaengine 框架由此登场。

二、dmaengine 框架定位:内核中的 DMA 抽象层

dmaengine 是 Linux 内核提供的 DMA 子系统,位于设备驱动与具体 DMA 控制器之间。它的设计目标可以概括为三点:统一接口、分离关注点、支持异步传输。

  • 统一接口:上层驱动通过 dma_request_chan()、dmaengine_prep_slave_sg()、dmaengine_submit()、dma_async_issue_pending() 等 API 发起传输,无需关心底层是哪种控制器。
  • 分离关注点:DMA 控制器驱动(provider)只负责实现硬件操作,外设驱动(consumer)只负责描述传输需求,二者通过通道和描述符解耦。
  • 异步传输:提交与完成分离,驱动可继续处理其他任务,传输完成后通过回调或完成量通知。

从内核层次看,dmaengine 向下对接 DMA 控制器驱动,向上服务 SPI、I2S、UART、MMC、网络等外设驱动,同时向用户空间暴露 dma_async_tx 相关能力。它既是资源管理器,也是传输调度器,更是性能调优的关键观测点。

dmaengine 框架层次与数据通路 外设驱动(Consumer) SPI · I2S · UART · MMC · 网络 · 音频 —— 描述传输需求,申请通道 dma_request_chan / prep_slave_sg / submit dmaengine 核心层 通道管理 · 描述符队列 · 传输调度 · 回调与完成通知 · 能力查询 统一 API:dmaengine_prep_* / dmaengine_submit / dma_async_issue_pending dma_device 操作集 DMA 控制器驱动(Provider) 实现 device_prep_slave_sg / device_issue_pending / device_tx_status 等回调 寄存器读写 / 中断处理 DMA 硬件控制器 多通道 · 优先级 · 突发长度 · 硬件握手 · 分散/聚集 · 循环缓冲 数据流方向:外设 ↔ 内存,全程不占用 CPU 核心搬运周期

三、课程目标与学习路径

本课程围绕"接口理解—驱动实现—性能测试—调优复盘"的主线展开,目标是让学习者能够独立完成 DMA 通道申请、传输描述符构建、异步提交与完成处理,并能通过测试手段定位吞吐瓶颈、延迟抖动和通道争用问题。

学习路径建议按以下顺序推进:

  1. 基础认知:理解 DMA 原理、dmaengine 分层与核心数据结构(dma_chan、dma_desc、dma_slave_config)。
  2. 接口掌握:熟练使用申请、配置、准备、提交、触发、等待完成六类 API。
  3. 驱动实践:编写一个基于 dmaengine 的外设传输驱动,处理回调与错误路径。
  4. 性能测试:设计吞吐、延迟、CPU 占用率三类指标,搭建可复现测试环境。
  5. 调优复盘:结合突发长度、通道优先级、缓存一致性、中断合并等参数进行优化。

四、核心概念速查清单

概念 含义 典型接口/参数
DMA 通道 控制器上可独立调度的传输资源 dma_request_chan()
从设备配置 描述外设地址、方向、突发等属性 dma_slave_config
描述符 一次传输任务的硬件可识别描述 dma_async_tx_descriptor
提交与触发 将描述符入队并启动传输 dmaengine_submit() / dma_async_issue_pending()
完成通知 传输结束后的回调或等待 dma_cookie / 回调函数
分散/聚集 跨多个不连续内存块的传输 scatterlist / sg_table

五、常见误区与复盘提示

误区一:认为 DMA 一定比 CPU 搬运快。 对于小数据量或高碎片化传输,DMA 的配置与中断开销可能超过直接拷贝,需结合数据规模评估。

误区二:忽略缓存一致性。 DMA 绕过 CPU 缓存,若未正确使用 dma_map_single() 或 dma_sync_single_for_cpu(),会出现数据陈旧或丢失。

误区三:只测吞吐不看延迟。 流式场景关注吞吐,控制类场景更关注单次传输延迟与抖动,测试指标必须与业务匹配。

建议在进入后续章节前,先在自己的开发板上确认 DMA 控制器节点、可用通道数量以及内核是否使能 CONFIG_DMADEVICES,这将直接影响后续实验的可操作性。

# 查看系统中已注册的 DMA 控制器与通道
ls /sys/class/dma/
cat /proc/interrupts | grep -i dma

# 确认内核配置
zcat /proc/config.gz | grep -E "CONFIG_DMADEVICES|CONFIG_DMA_ENGINE"