DMA控制器编程与PCIe数据搬运优化工程案例 - 第1节
1.1 DMA 工作原理
直接存储器访问(Direct Memory Access, DMA)是一种允许硬件子系统(外设或 DMA 控制器)直接读写系统内存,而无需 CPU 参与数据搬运的机制。DMA 控制器是系统中的专用硬件引擎,负责管理数据传输的全过程。
核心思想:将数据搬运任务从 CPU 卸载到专用硬件,使 CPU 可以专注于计算和控制逻辑。
DMA 传输的基本流程
- CPU 配置 DMA 控制器:CPU 设置源地址、目的地址、传输长度、传输方向等参数。
- DMA 控制器请求总线控制权:DMA 向总线仲裁器发出总线请求信号(HRQ/BREQ)。
- 总线授权:CPU 或总线仲裁器释放总线控制权,DMA 获得总线主控能力。
- DMA 执行数据传输:DMA 控制器直接驱动地址总线和数据总线,完成数据搬运。
- 传输完成通知:DMA 发送中断信号(INT)通知 CPU 传输结束。
1.2 DMA 与 CPU 数据搬运对比
理解 DMA 与 CPU 搬运数据的本质差异,是掌握 DMA 编程的基础。下表从多个维度对比两种方式:
| 对比维度 | CPU 直接搬运 | DMA 控制器搬运 |
|---|---|---|
| 执行主体 | CPU 核心(执行 load/store 指令) | DMA 控制器(专用硬件状态机) |
| CPU 占用率 | 100%(搬运期间无法执行其他任务) | 仅配置和中断处理阶段占用(通常 < 5%) |
| 数据传输速率 | 受限于 CPU 指令执行速度(通常 1~5 GB/s) | 接近总线理论带宽(PCIe Gen4 x16 可达 32 GB/s) |
| 适用场景 | 小数据量(< 64 KB)、非连续地址、频繁修改 | 大数据块(≥ 64 KB)、连续地址、周期性传输 |
| 功耗效率 | 高(CPU 核心全速运行) | 低(专用硬件,能效比高 10~100 倍) |
| 编程复杂度 | 简单(memcpy / 循环赋值) | 中等(需配置描述符、处理中断、管理链表) |
工程经验:在 PCIe 高速数据采集系统中,CPU 搬运 1 MB 数据约需 50 μs,而 DMA 仅需 2~3 μs(含配置开销),性能提升 15~25 倍。
CPU 搬运 vs DMA 搬运的代码对比
// CPU 直接搬运(使用 memcpy)
void cpu_memcpy(uint32_t *src, uint32_t *dst, size_t size) {
memcpy(dst, src, size); // CPU 100% 占用,无法响应其他中断
}
// DMA 搬运(以 Xilinx AXI DMA 为例)
void dma_transfer(uint32_t *src, uint32_t *dst, size_t size) {
// 1. 配置 DMA 描述符
dma_desc_t desc;
desc.src_addr = (uint32_t)src;
desc.dst_addr = (uint32_t)dst;
desc.length = size;
desc.next = NULL; // 单次传输
// 2. 启动 DMA(CPU 仅需 3 条写操作)
write_reg(DMA_SRC_ADDR, desc.src_addr);
write_reg(DMA_DST_ADDR, desc.dst_addr);
write_reg(DMA_CTRL, DMA_START | DMA_IE); // 启动并使能中断
// 3. CPU 立即返回,可执行其他任务
// DMA 完成后触发中断,在 ISR 中处理结果
}
1.3 DMA 传输类型
根据数据源和目的地的不同,DMA 传输分为三种基本类型。在 PCIe 系统中,这三种类型对应不同的应用场景。
三种传输类型详解
传输类型选择指南
| 传输类型 | 典型场景 | PCIe 方向 | DMA 描述符配置要点 |
|---|---|---|---|
| 内存 → 内存 | FPGA 内部数据重排、缓冲区交换 | 不经过 PCIe(片内) | src/dst 均为内存地址,无需 PCIe TLP |
| 外设 → 内存 | 高速 ADC 数据采集、雷达信号捕获 | PCIe 上行(Endpoint → Root Complex) | src 为 PCIe BAR 地址,dst 为系统物理地址 |
| 内存 → 外设 | 任意波形发生器、GPU 纹理上传 | PCIe 下行(Root Complex → Endpoint) | src 为系统物理地址,dst 为 PCIe BAR 地址 |
常见误区:初学者常将 "DMA 读/写" 与 "PCIe 读/写" 混淆。在 PCIe 语境下,DMA Read 是指 DMA 控制器从外设读取数据到内存(外设→内存),而非从内存读取数据。务必根据数据流动方向而非操作名称判断。
DMA 传输类型与中断处理
三种传输类型在中断处理上也有差异:
- 内存→内存:通常只需一次完成中断,无需考虑数据溢出。
- 外设→内存:需处理"数据可用"中断和"传输完成"中断,防止 FIFO 溢出。
- 内存→外设:需处理"传输完成"中断和"外设就绪"握手,防止 FIFO 下溢。
// 外设→内存 DMA 中断服务程序示例(伪代码)
void dma_isr(void) {
uint32_t status = read_reg(DMA_STATUS);
if (status & DMA_DONE) {
// 当前传输完成,处理已接收数据
process_buffer(current_desc->dst_addr, current_desc->length);
// 提交下一个描述符(循环缓冲)
submit_next_descriptor();
}
if (status & DMA_ERROR) {
// 处理总线错误或超时
handle_dma_error();
}
// 清除中断标志
write_reg(DMA_STATUS, status);
}