1. 课程导论:DMA 与 mmap 在 Linux 驱动中的核心价值

在嵌入式 Linux 系统中,平台驱动 (Platform Driver) 是连接硬件外设与内核框架的标准总线模型。当外设需要搬运大量数据(如摄像头帧、音频流、高速 ADC 采样)时,单纯依赖 CPU 逐字节拷贝会严重拖累系统吞吐量。本课程聚焦两大核心技术——DMA(直接内存访问)与 mmap(内存映射),揭示它们如何在平台驱动中协同工作,实现零拷贝、低延迟的数据传输路径。

核心观点: DMA 解决“谁搬数据”的问题,mmap 解决“用户态如何高效访问数据”的问题。两者结合,可让外设数据直接流入用户空间,CPU 仅做配置与同步。

1.1 DMA 在 Linux 驱动中的核心价值

DMA 控制器独立于 CPU,能在内存与外设之间、或内存与内存之间直接搬运数据。在平台驱动中注册 DMA 通道后,一次传输可达到数百 MB/s 甚至 GB/s 级别,且 CPU 仅在传输开始和完成时介入。

对比维度 PIO(程序控制 I/O) DMA(直接内存访问)
数据搬运主体 CPU 逐字拷贝 DMA 控制器硬件搬运
CPU 占用率 高(传输期间被占满) 低(仅启动和中断处理)
吞吐量上限 受限于 CPU 主频与缓存 受限于总线带宽(通常更高)
适用场景 小数据量、控制寄存器访问 大数据块、流式数据(音视频、网络)
实战提示: 在嵌入式平台(如 i.MX、STM32MP1、Zynq)上,DMA 通常由硬件描述(Device Tree)声明,驱动通过 dma_request_chan() 获取通道,使用 dmaengine_prep_slave_sg() 准备传输描述符。
DMA 传输流程示意图 CPU 配置 DMA 描述符 DMA 控制器 硬件自动搬运 外设 / 内存 数据源 / 目的地 DMA 传输流程 ① CPU 构建 scatter-gather 链表 → ② 启动 DMA → ③ DMA 独立搬运 → ④ 传输完成触发中断 中断通知 CPU

1.2 mmap 在 Linux 驱动中的核心价值

传统用户态读取设备数据需要经过 read() 系统调用 → 内核缓冲区 → 拷贝到用户缓冲区,至少两次拷贝。mmap 将内核空间的一段物理内存或 DMA 缓冲区直接映射到用户进程的虚拟地址空间,用户态程序可以像访问普通数组一样直接操作数据,完全消除内核态与用户态之间的数据拷贝。

访问方式 数据拷贝次数 上下文切换 延迟
read() / write() 至少 2 次(内核↔用户) 每次调用 1 次系统调用 高(μs 级)
mmap() 0 次(直接映射) 仅映射时 1 次系统调用 低(ns 级访问)
注意事项: mmap 映射的物理内存必须连续(或通过 remap_pfn_range() 处理),且需要处理缓存一致性(Cache Coherency)。对于 DMA 缓冲区,通常使用 dma_alloc_coherent() 分配一致性内存,或使用 dma_map_single() 配合 DMA_FROM_DEVICE 方向进行同步。
mmap 与传统 read 对比 传统 read() 路径 用户缓冲区 内核缓冲区 拷贝 mmap 路径(零拷贝) 用户虚拟地址 DMA 物理内存 直接映射 mmap 优势总结 • 消除内核↔用户数据拷贝 • 减少系统调用次数 • 用户态直接操作 DMA 数据 • 适合大块数据、实时处理 • 配合 dma_mmap_coherent() 实现

1.3 DMA 与 mmap 协同:平台驱动的终极数据通路

在平台驱动中将 DMA 与 mmap 结合,形成一条完整的硬件→用户空间零拷贝链路:

  1. DMA 传输阶段: 外设数据通过 DMA 控制器直接写入预先分配的物理内存缓冲区(通常使用 dma_alloc_coherent() 分配)。
  2. mmap 映射阶段: 驱动实现 file_operations 中的 .mmap 回调,调用 dma_mmap_coherent() 将同一块物理内存映射到用户进程的虚拟地址空间。
  3. 用户态访问: 应用程序直接读取映射后的虚拟地址,获取最新 DMA 数据,无需任何拷贝。
典型应用场景: 工业相机采集、软件定义无线电(SDR)、高速数据采集卡、AI 推理预处理管线。在这些场景中,数据吞吐量常超过 500 MB/s,传统 read/write 方式完全无法满足实时性要求。
DMA + mmap 协同架构 用户空间 (User Space) 应用程序 mmap 映射区 访问 内核空间 (Kernel Space) Platform 驱动 DMA 引擎 控制 硬件层 (Hardware) DMA 控制器 外设 (SPI/I2S/CAM) 数据流

1.4 课程目标与前置知识要求

通过本课程,你将掌握以下核心能力:

  • 平台驱动框架: 理解 platform_driver 与 platform_device 的匹配机制,以及 Device Tree 中 DMA 与内存资源的描述方法。
  • DMA 引擎 API: 熟练使用 Linux DMA Engine 子系统(dma_request_chan、dmaengine_prep_slave_sg、dmaengine_submit、异步回调)。
  • mmap 实现: 在驱动中正确实现 .mmap 回调,使用 remap_pfn_range 或 dma_mmap_coherent 映射物理内存。
  • 协同实战: 编写一个完整的平台驱动,实现外设 DMA 传输数据到内核缓冲区,并通过 mmap 暴露给用户态程序直接读取。
前置知识领域 具体要求 推荐参考
C 语言 / 内核编程 熟悉指针、内存管理、内核模块编写 《Linux Device Drivers》第 1-2 章
Linux 驱动基础 理解 file_operations、字符设备、platform 总线 本系列前序课程《platform 驱动入门》
内存管理概念 了解虚拟地址、物理地址、页表、MMU 《深入理解 Linux 内核》第 8 章
硬件基础 了解 DMA 控制器基本工作原理、中断机制 芯片参考手册(如 i.MX8M TRM)
学习建议: 本课程包含大量可编译运行的示例代码。建议准备一块支持 DMA 的嵌入式开发板(如 i.MX8M Mini、STM32MP157、Raspberry Pi 4 + 外部 DMA 芯片),或使用 QEMU 的 virtio 平台进行模拟验证。