DMA基础:DMA控制器工作原理、DMA传输模式与CPU协同机制

2.1 DMA控制器工作原理

直接存储器访问(DMA)是一种允许硬件子系统直接访问系统内存的机制,无需CPU干预。DMA控制器是系统中的专用硬件模块,负责在源地址与目的地址之间搬运数据,从而将CPU从繁重的数据拷贝任务中解放出来。

核心组件

组件 功能描述
源地址寄存器 存储数据读取的起始内存地址或外设地址
目的地址寄存器 存储数据写入的目标内存地址或外设地址
传输计数寄存器 记录待传输的数据单元数量(字节/字/块)
控制寄存器 配置传输方向、模式、宽度、优先级等参数
状态寄存器 指示传输完成、错误、中断等状态信息
请求/应答逻辑 处理来自外设的DMA请求,向总线仲裁器发起总线控制权申请

基本工作流程

  1. 初始化:CPU配置DMA控制器的源地址、目的地址、传输长度和传输模式。
  2. 请求:外设(如ADC、UART、摄像头)通过硬件信号线向DMA控制器发出传输请求。
  3. 仲裁:DMA控制器向总线仲裁器申请总线控制权,获得授权后接管系统总线。
  4. 传输:DMA控制器在源与目的之间直接搬运数据,每次传输后更新地址和计数寄存器。
  5. 完成:传输计数归零后,DMA控制器释放总线,向CPU发出中断信号或触发回调。
关键指标:DMA传输延迟通常为几个总线时钟周期,远低于CPU逐字节拷贝的软件开销。在嵌入式系统中,DMA可将CPU的I/O数据搬运开销从80%降低至5%以下。
DMA控制器内部架构与数据流 CPU 系统总线(System Bus) DMA 控制器 源地址寄存器 目的地址寄存器 传输计数 控制/状态 请求/应答逻辑 内存 外设 DMA接管总线后 CPU暂停访问内存

2.2 DMA传输模式

根据应用场景的不同,DMA控制器支持多种传输模式。选择合适的模式可以显著提升系统吞吐量并降低CPU负载。

三种核心传输模式对比

模式 工作方式 适用场景 优点 缺点
单次传输 每次触发传输一个数据单元(字节/字),完成后停止 低速外设、单次数据采集 实现简单,资源占用少 频繁重新配置,效率低
循环传输 传输完成后自动重置地址和计数,持续循环 音频流、ADC连续采样、环形缓冲区 无需CPU干预,实时性好 需处理缓冲区覆盖问题
链表传输 通过描述符链表链接多个传输任务,自动切换 复杂数据流、多通道采集、分散/聚集I/O 灵活高效,支持任意地址组合 描述符管理复杂,内存开销大

单次传输模式

单次传输是最基本的DMA模式。CPU配置一次传输参数后,DMA控制器在收到外设请求时传输一个数据单元,然后停止并等待下一次配置。该模式适用于按键、温度传感器等低速设备。

循环传输模式

循环传输模式下,DMA控制器在传输计数归零后自动将地址和计数寄存器重置为初始值,形成环形缓冲区。音频播放、麦克风采集等连续数据流场景广泛使用此模式。开发者需注意缓冲区半满中断和覆盖保护机制。

实践建议:在循环传输中,通常使用双缓冲(ping-pong buffer)技术:一块缓冲区用于DMA写入,另一块供CPU读取,交替使用以避免数据竞争。

链表传输模式

链表传输(也称为描述符链或散聚DMA)是最高级的传输模式。DMA控制器维护一个描述符链表,每个描述符包含源地址、目的地址、传输长度和下一个描述符指针。当前传输完成后,硬件自动加载下一个描述符,无需CPU介入。

DMA链表传输模式(描述符链) 描述符 1 源: 0x4000_2000 目的: 0x8000_0000 长度: 1024 字节 下一跳: 0x2000_0100 描述符 2 源: 0x4000_3000 目的: 0x8000_0400 长度: 512 字节 下一跳: 0x2000_0200 描述符 3 源: 0x4000_4000 目的: 0x8000_0600 长度: 2048 字节 下一跳: NULL(结束) DMA 控制器(硬件自动加载) 链表传输优势 一次配置多个传输任务,硬件自动切换描述符,CPU仅在链尾触发中断

2.3 DMA与CPU的协同机制

DMA与CPU共享系统总线和内存资源,必须建立高效的协同机制以避免冲突和保证数据一致性。

总线仲裁

当DMA控制器和外设同时请求总线时,总线仲裁器根据优先级策略决定谁获得总线控制权。常见策略包括:

  • 固定优先级:DMA优先级高于CPU,适用于实时性要求高的场景
  • 循环优先级:各主设备轮流获得总线,保证公平性
  • 动态优先级:根据当前负载动态调整,实现最优吞吐

Cache一致性

DMA直接访问物理内存,而CPU可能使用Cache缓存数据。当DMA修改了内存内容,CPU的Cache中可能仍保留旧数据,导致不一致问题。解决方案包括:

方法 操作 适用场景
Cache刷新(Flush) DMA传输前将CPU Cache写回内存 CPU写、DMA读
Cache失效(Invalidate) DMA传输后将CPU Cache标记为无效 DMA写、CPU读
非Cache内存 将DMA缓冲区映射为不可Cache区域 高频DMA传输
硬件一致性 使用支持Cache Coherent的总线协议 高性能SoC

中断与轮询

DMA传输完成后,通过中断通知CPU是最常用的方式。但在高频率小数据量场景下,中断开销可能过高,此时可考虑轮询状态寄存器。

性能陷阱:在中断服务程序中执行复杂操作会阻塞后续DMA传输。建议ISR中仅设置标志位或唤醒等待队列,将数据处理推迟到任务上下文中执行。

典型协同流程

  1. CPU分配DMA缓冲区并确保Cache一致性(Flush/Invalidate)
  2. CPU配置DMA控制器参数并启动传输
  3. DMA控制器独立传输数据,CPU继续执行其他任务
  4. DMA传输完成,触发中断
  5. CPU在中断上下文中标记数据就绪,唤醒处理任务
  6. CPU处理数据前执行Cache失效操作(若DMA写入内存)
DMA与CPU协同工作流程 时间 CPU:配置DMA CPU:执行其他任务 CPU:中断处理 空闲 DMA:数据传输中 空闲 中断 协同关键点 DMA传输期间CPU可并行执行任务,中断触发后CPU处理数据 Cache一致性操作必须在DMA传输前后正确执行

2.4 实际应用中的DMA配置示例

以下代码展示在Linux内核中配置DMA循环传输的典型流程,用于音频采集场景:

#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>

struct dma_chan *chan;
struct dma_slave_config config;
struct dma_async_tx_descriptor *tx;
dma_cookie_t cookie;

/* 1. 请求DMA通道 */
chan = dma_request_chan(dev, "audio_rx");
if (IS_ERR(chan))
    return PTR_ERR(chan);

/* 2. 配置从设备参数 */
memset(&config, 0, sizeof(config));
config.direction = DMA_DEV_TO_MEM;
config.src_addr = (phys_addr_t)audio_base + AUDIO_FIFO;
config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
config.src_maxburst = 4;
dmaengine_slave_config(chan, &config);

/* 3. 准备循环传输描述符 */
tx = dmaengine_prep_dma_cyclic(chan,
                               dma_map_single(dev, buf, buf_size, DMA_FROM_DEVICE),
                               buf_size,
                               period_size,
                               DMA_DEV_TO_MEM,
                               DMA_PREP_INTERRUPT);
if (!tx)
    goto err_free;

/* 4. 注册完成回调 */
tx->callback = audio_dma_complete;
tx->callback_param = audio_dev;

/* 5. 提交并启动传输 */
cookie = dmaengine_submit(tx);
dma_async_issue_pending(chan);
性能优化要点:
• 使用 dma_map_single 确保Cache一致性,避免数据错乱
• period_size 决定中断频率,通常设置为缓冲区大小的1/4或1/2
• 回调函数中应使用 tasklet 或 workqueue 处理数据,避免在中断上下文阻塞