4. DMA基础原理
直接存储器访问(Direct Memory Access, DMA)是块设备驱动性能优化的核心硬件机制。在VxWorks系统中,DMA控制器允许外设与内存之间直接传输数据,无需CPU逐字节搬运,从而释放CPU去处理更高优先级的任务。本节将深入剖析DMA控制器的工作原理、三种主流传输模式以及DMA与CPU的并行执行模型。
4.1 DMA控制器工作原理
DMA控制器本质上是一个专用的硬件状态机,负责管理总线仲裁、地址生成和传输计数。一次典型的DMA传输包含以下关键阶段:
- 请求阶段:外设(如存储控制器、网络接口)通过硬件信号线向DMA控制器发出传输请求。
- 仲裁阶段:DMA控制器向CPU请求总线控制权。若总线空闲或CPU处于空闲周期,DMA获得总线授权。
- 传输阶段:DMA控制器在源地址与目标地址之间驱动数据总线,每传输一个单元(字节/字/双字)后自动递增地址并递减计数器。
- 完成阶段:当计数器归零时,DMA控制器向CPU发出中断信号,通知传输完成。
4.2 DMA传输模式
根据块设备驱动的不同场景,DMA控制器支持三种核心传输模式。下表从传输粒度、总线占用和适用场景三个维度进行对比:
| 传输模式 | 传输粒度 | 总线占用 | CPU干预 | 典型应用 |
|---|---|---|---|---|
| 单次传输 | 1 个数据单元/请求 | 低(释放快) | 每次传输后中断 | 低速外设、控制信号 |
| 突发传输 | 4/8/16 个数据单元/请求 | 中(固定长度) | 突发结束后中断 | 块设备、存储控制器 |
| 链式传输 | 多个描述符链接 | 高(连续传输) | 整链完成后中断 | 大数据块、文件系统 |
4.2.1 单次传输模式
单次传输模式下,DMA控制器每次总线授权只传输一个数据单元(如一个32位字)。传输完成后立即释放总线,并向CPU发送中断。这种模式总线占用时间最短,但中断频率高,适合数据量小或对延迟敏感的控制类传输。
4.2.2 突发传输模式
突发传输模式允许DMA控制器在一次总线授权中连续传输多个数据单元(突发长度通常可配置为4、8或16)。传输期间CPU无法访问总线,但中断频率显著降低。对于块设备驱动,突发传输是平衡吞吐量与CPU可用性的首选模式。
4.2.3 链式传输模式
链式传输(也称为描述符环或散聚传输)是最高效的模式。DMA控制器维护一个描述符链表,每个描述符包含源地址、目标地址、传输长度和下一个描述符指针。CPU只需初始化链表首地址,DMA控制器即可自动遍历整个链表完成所有传输,仅在整链完成后触发一次中断。
4.3 DMA与CPU的并行性
DMA的核心价值在于实现数据传输与CPU计算的并行执行。在理想情况下,当DMA控制器在总线上搬运数据时,CPU可以同时执行指令、处理中断或运行操作系统调度。这种并行性依赖于以下硬件机制:
- 总线分时复用:现代SoC采用多层总线架构(如AMBA AXI),DMA与CPU可同时访问不同的总线从设备(如DMA访问内存,CPU访问缓存)。
- 缓存一致性协议:当DMA将数据写入内存时,CPU缓存中的对应行会被标记为无效(Invalidate),下次访问时从内存重新加载,保证数据一致性。
- 写缓冲与读预取:CPU的写缓冲允许DMA读取最新写入的数据,而CPU的硬件预取器可提前加载DMA即将写入的内存区域。
cacheDmaMalloc() 分配一致性内存。
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);