4. DMA基础原理

直接存储器访问(Direct Memory Access, DMA)是块设备驱动性能优化的核心硬件机制。在VxWorks系统中,DMA控制器允许外设与内存之间直接传输数据,无需CPU逐字节搬运,从而释放CPU去处理更高优先级的任务。本节将深入剖析DMA控制器的工作原理、三种主流传输模式以及DMA与CPU的并行执行模型。

4.1 DMA控制器工作原理

DMA控制器本质上是一个专用的硬件状态机,负责管理总线仲裁、地址生成和传输计数。一次典型的DMA传输包含以下关键阶段:

  1. 请求阶段:外设(如存储控制器、网络接口)通过硬件信号线向DMA控制器发出传输请求。
  2. 仲裁阶段:DMA控制器向CPU请求总线控制权。若总线空闲或CPU处于空闲周期,DMA获得总线授权。
  3. 传输阶段:DMA控制器在源地址与目标地址之间驱动数据总线,每传输一个单元(字节/字/双字)后自动递增地址并递减计数器。
  4. 完成阶段:当计数器归零时,DMA控制器向CPU发出中断信号,通知传输完成。
关键指标:DMA传输延迟通常由总线仲裁时间 + 传输时间 + 中断响应时间三部分组成。在VxWorks中,通过配置DMA通道优先级和中断亲和性,可将仲裁与中断延迟控制在微秒级。
DMA控制器内部状态机 空闲 (IDLE) 请求仲裁 总线传输 完成中断 状态转换由硬件信号触发,VxWorks通过DMA描述符链管理多个传输任务

4.2 DMA传输模式

根据块设备驱动的不同场景,DMA控制器支持三种核心传输模式。下表从传输粒度、总线占用和适用场景三个维度进行对比:

传输模式 传输粒度 总线占用 CPU干预 典型应用
单次传输 1 个数据单元/请求 低(释放快) 每次传输后中断 低速外设、控制信号
突发传输 4/8/16 个数据单元/请求 中(固定长度) 突发结束后中断 块设备、存储控制器
链式传输 多个描述符链接 高(连续传输) 整链完成后中断 大数据块、文件系统

4.2.1 单次传输模式

单次传输模式下,DMA控制器每次总线授权只传输一个数据单元(如一个32位字)。传输完成后立即释放总线,并向CPU发送中断。这种模式总线占用时间最短,但中断频率高,适合数据量小或对延迟敏感的控制类传输。

4.2.2 突发传输模式

突发传输模式允许DMA控制器在一次总线授权中连续传输多个数据单元(突发长度通常可配置为4、8或16)。传输期间CPU无法访问总线,但中断频率显著降低。对于块设备驱动,突发传输是平衡吞吐量与CPU可用性的首选模式。

VxWorks优化建议:在块设备驱动中,建议将突发长度配置为缓存行大小(通常64字节)的整数倍,以充分利用CPU缓存一致性协议,减少DMA与缓存之间的同步开销。

4.2.3 链式传输模式

链式传输(也称为描述符环或散聚传输)是最高效的模式。DMA控制器维护一个描述符链表,每个描述符包含源地址、目标地址、传输长度和下一个描述符指针。CPU只需初始化链表首地址,DMA控制器即可自动遍历整个链表完成所有传输,仅在整链完成后触发一次中断。

链式DMA描述符结构 描述符 1 源地址: 0x1000 目标地址: 0x2000 长度: 512B 下一描述符: → 描述符 2 源地址: 0x1100 目标地址: 0x2100 长度: 256B 下一描述符: → 描述符 3 源地址: 0x1200 目标地址: 0x2200 长度: 1024B 下一描述符: NULL DMA控制器自动遍历链表,每完成一个描述符自动加载下一个,整链完成后触发一次中断 VxWorks中通过 vxbDmaDescAdd() 构建描述符链,支持动态追加

4.3 DMA与CPU的并行性

DMA的核心价值在于实现数据传输与CPU计算的并行执行。在理想情况下,当DMA控制器在总线上搬运数据时,CPU可以同时执行指令、处理中断或运行操作系统调度。这种并行性依赖于以下硬件机制:

  • 总线分时复用:现代SoC采用多层总线架构(如AMBA AXI),DMA与CPU可同时访问不同的总线从设备(如DMA访问内存,CPU访问缓存)。
  • 缓存一致性协议:当DMA将数据写入内存时,CPU缓存中的对应行会被标记为无效(Invalidate),下次访问时从内存重新加载,保证数据一致性。
  • 写缓冲与读预取:CPU的写缓冲允许DMA读取最新写入的数据,而CPU的硬件预取器可提前加载DMA即将写入的内存区域。
常见陷阱:在VxWorks中,如果DMA缓冲区与CPU缓存行对齐不当,会导致伪共享(False Sharing)问题——DMA修改一个缓存行中的部分数据,迫使CPU刷新整个缓存行,严重降低并行效率。解决方案是将DMA缓冲区按缓存行大小(通常64字节)对齐,并使用 cacheDmaMalloc() 分配一致性内存。
DMA与CPU并行执行时序 t0 t1 t2 t3 CPU 任务A执行 中断处理 任务B执行 DMA DMA传输(链式) DMA空闲 并行执行区:CPU任务A 与 DMA传输同时进行

4.4 VxWorks DMA编程模型

在VxWorks中,DMA操作通过标准API封装,驱动开发者无需直接操作硬件寄存器。核心接口包括:

API函数 功能描述 所属模块
vxbDmaChannelAlloc() 分配DMA通道并绑定中断 VxBus DMA库
vxbDmaDescInit() 初始化单个DMA描述符 VxBus DMA库
vxbDmaDescAdd() 将描述符加入传输链 VxBus DMA库
vxbDmaStart() 启动DMA传输 VxBus DMA库
vxbDmaStatusGet() 查询传输状态 VxBus DMA库

以下是一个典型的链式DMA传输代码片段:

/* VxWorks 链式DMA传输示例 */
VXB_DMA_CHANNEL *dmaChan;
VXB_DMA_DESC    desc[3];
void            *srcBuf, *dstBuf;

/* 1. 分配DMA通道 */
dmaChan = vxbDmaChannelAlloc(pDev, DMA_CHANNEL_0, DMA_DIR_MEM_TO_MEM);
if (dmaChan == NULL) {
    return ERROR;
}

/* 2. 初始化三个描述符,形成链 */
vxbDmaDescInit(&desc[0], srcBuf, dstBuf, 512, DMA_DESC_FLAG_CHAIN);
vxbDmaDescInit(&desc[1], srcBuf + 512, dstBuf + 512, 256, DMA_DESC_FLAG_CHAIN);
vxbDmaDescInit(&desc[2], srcBuf + 768, dstBuf + 768, 1024, DMA_DESC_FLAG_INT_EN);

/* 3. 链接描述符 */
vxbDmaDescAdd(dmaChan, &desc[0]);
vxbDmaDescAdd(dmaChan, &desc[1]);
vxbDmaDescAdd(dmaChan, &desc[2]);

/* 4. 启动传输(非阻塞,立即返回) */
vxbDmaStart(dmaChan);

/* 5. CPU可在此处执行其他任务 */
doOtherWork();

/* 6. 等待传输完成(中断驱动) */
semTake(dmaDoneSem, WAIT_FOREVER);
性能要点:在VxWorks块设备驱动中,建议将DMA描述符数量设置为至少4个,形成环形缓冲区(Ring Buffer)。这样可以在DMA处理当前描述符时,CPU预先填充后续描述符,实现流水线并行,最大化总线利用率。