Linux DMA描述符链与循环缓冲驱动开发及调优 - 第1节
1.1 DMA 工作原理
直接存储器访问(Direct Memory Access, DMA)是一种允许硬件设备直接与系统内存交换数据而无需 CPU 逐字节干预的机制。在嵌入式 Linux 系统中,DMA 是驱动性能的关键使能器,尤其适用于大数据块搬运、高速外设(如以太网、MMC、显示控制器)以及实时性要求高的场景。
核心思想:CPU 只需发起传输请求,DMA 控制器(DMAC)接管总线控制权,完成数据搬运后通过中断或轮询通知 CPU。CPU 在传输期间可执行其他任务,实现计算与 I/O 并行。
DMA 传输的基本流程
- CPU 配置 DMA 通道:设置源地址、目的地址、传输宽度、突发大小、传输方向(内存→外设、外设→内存、内存→内存)。
- CPU 启动 DMA:写控制寄存器使能通道,DMAC 获得总线控制权。
- DMAC 执行传输:按配置从源地址读取数据,写入目的地址,每完成一个传输单元(或整个块)更新地址指针和计数器。
- 传输完成通知:DMAC 触发中断或置位状态寄存器,CPU 响应并处理数据。
1.2 DMA 与 CPU 交互模式
DMA 与 CPU 的交互方式直接影响系统吞吐量和实时性。嵌入式 Linux 驱动中常见的交互模式有三种:中断驱动模式、轮询模式和描述符链表模式。下表对比了它们的核心差异:
| 交互模式 | CPU 负载 | 实时性 | 适用场景 | 典型延迟 |
|---|---|---|---|---|
| 中断驱动 | 低(仅传输完成时响应) | 高(立即响应完成事件) | 低速外设、非连续传输 | 几微秒 ~ 几十微秒 |
| 轮询模式 | 高(持续检查状态寄存器) | 中(取决于轮询频率) | 极高速、可预测的短传输 | 纳秒级(但浪费 CPU) |
| 描述符链表 | 极低(批量提交后等待一次中断) | 高(支持链式连续传输) | 高速流式数据(网络、音视频) | 微秒级(批量完成) |
驱动开发建议:在 Linux DMA 引擎框架中,
dmaengine_prep_slave_sg() 和 dmaengine_submit() 配合描述符链表模式是最常用的高性能路径。轮询模式仅在调试或极低延迟控制回路中使用。
中断驱动模式详解
中断驱动是最经典的 DMA 交互方式。DMAC 完成一次传输后向 CPU 发送中断信号,CPU 在中断服务程序(ISR)中处理数据并准备下一次传输。该模式的关键性能指标是中断延迟和中断合并。
1.3 DMA 传输类型
根据数据流向和传输结构,DMA 传输可分为以下三种基本类型。理解这些类型是配置描述符链表和循环缓冲的基础。
1. 内存到内存(Memory-to-Memory, M2M)
- 数据流:系统内存的一个区域 → 系统内存的另一个区域。
- 典型用途:数据拷贝、缓冲区移动、图像帧搬运。
- 特点:无需外设参与,DMAC 完全控制总线;通常使用单次或突发传输。
- Linux 驱动接口:
dmaengine_prep_dma_memcpy()。
2. 内存到外设(Memory-to-Device, M2P)
- 数据流:系统内存 → 外设 FIFO/寄存器。
- 典型用途:发送以太网数据包、LCD 显示刷新、音频播放。
- 特点:外设通常有固定的数据宽度和流控信号;需要处理外设就绪状态。
- Linux 驱动接口:
dmaengine_prep_slave_sg()配合DMA_MEM_TO_DEV方向。
3. 外设到内存(Device-to-Memory, P2M)
- 数据流:外设 FIFO/寄存器 → 系统内存。
- 典型用途:接收以太网数据包、摄像头数据采集、音频录制。
- 特点:外设作为数据源,DMAC 需响应外设请求信号;常与循环缓冲结合实现无丢包采集。
- Linux 驱动接口:
dmaengine_prep_slave_sg()配合DMA_DEV_TO_MEM方向。
| 传输类型 | 源端 | 目的端 | 外设参与 | 典型带宽 | 描述符需求 |
|---|---|---|---|---|---|
| M2M | 内存 | 内存 | 无 | 最高(全内存带宽) | 单描述符或链式 |
| M2P | 内存 | 外设 | 有(流控/握手) | 受外设速率限制 | 循环缓冲描述符链 |
| P2M | 外设 | 内存 | 有(流控/握手) | 受外设速率限制 | 循环缓冲描述符链 |
常见误区:M2M 传输虽然带宽最高,但在嵌入式系统中会占用系统总线,影响其他总线主控(如 CPU 取指、GPU 渲染)的访问。实际项目中应优先使用 M2P/P2M 释放 CPU 负载,而非单纯追求 M2M 的峰值带宽。
传输粒度与突发传输
DMA 控制器支持多种传输粒度,驱动配置时需根据外设 FIFO 深度和系统总线宽度选择最优参数:
- 单次传输(Single Transfer):每次总线事务传输 1 个单元(如 1 字节、2 字节、4 字节)。适合外设 FIFO 较浅或流控严格的场景。
- 突发传输(Burst Transfer):一次总线事务连续传输多个单元(如 4、8、16 次)。可显著提高总线利用率,但可能增加外设 FIFO 溢出风险。
- 临界值(Critical Value):外设 FIFO 的阈值设置,决定何时触发 DMA 请求。例如 FIFO 半满时请求一次突发传输。
| 参数 | 描述 | 典型值 | 性能影响 |
|---|---|---|---|
| 传输宽度 | 每次总线事务的数据位宽 | 8/16/32/64 位 | 宽度越大,单次吞吐越高 |
| 突发长度 | 一次突发传输包含的单元数 | 4 / 8 / 16 | 长度越大,总线效率越高,但延迟增加 |
| FIFO 阈值 | 触发 DMA 请求的 FIFO 水位 | 1/4、1/2、3/4 | 阈值越低,响应越快,但中断频率高 |
性能调优要点:在循环缓冲模式下,建议将突发长度设置为与 cache line 大小(通常 32 或 64 字节)对齐,避免非对齐访问导致性能下降。同时,FIFO 阈值应保证在 DMA 响应延迟内不会发生下溢(M2P)或上溢(P2M)。