4. DMA与PIO:硬件如何帮我们搬数据

说到内存拷贝,很多人第一反应就是 memcpy。但你想过没有,CPU 其实并不适合干这种「搬砖」的活。它擅长的是计算、判断、分支预测。你让一个数学家去搬砖,是不是有点浪费?

我早年做嵌入式系统时,就踩过这个坑。一个视频采集卡,CPU 占用率飙到 90%,结果一查,全是 PIO 模式在作祟。后来换成 DMA,CPU 占用直接降到 15%。嗯,这就是我们今天要聊的核心——DMA 和 PIO 的区别。

4.1 PIO:CPU 亲自下场搬数据

PIO,全称 Programmed I/O,编程输入输出。说白了,就是 CPU 亲自下场,一条指令一条指令地把数据从外设搬到内存,或者从内存搬到外设。

举个例子,你要从硬盘读一个扇区(512 字节)到内存:

// PIO 模式下的硬盘读取(伪代码)
for (int i = 0; i < 512; i++) {
    // CPU 从硬盘数据端口读一个字节
    byte data = inb(0x1F0);
    // CPU 把这个字节写入内存缓冲区
    buffer[i] = data;
}

你看,每个字节都要 CPU 亲自参与。512 字节还好,如果是 1MB 呢?CPU 就完全被绑死在 I/O 上了,没法干别的。

PIO 的核心问题:CPU 在搬运数据期间,无法执行其他任务。这叫「忙等」(busy waiting)。

我在项目中遇到过一个问题:一个老旧的串口设备,驱动用的是 PIO 模式。每次收发几百字节数据,CPU 就卡死在那。后来我换成 DMA,问题迎刃而解。说白了,PIO 只适合小数据量、低频率的场景。

4.2 DMA:硬件帮你搬,CPU 去喝茶

DMA,全称 Direct Memory Access,直接内存访问。它引入了一个专门的硬件——DMA 控制器(DMAC),专门负责搬数据。

CPU 只需要告诉 DMA 控制器三件事:

  • 源地址:数据从哪来
  • 目标地址:数据搬到哪去
  • 数据长度:搬多少字节

然后 CPU 就可以去干别的事了。DMA 控制器自己完成搬运,搬运完成后发一个中断通知 CPU。

// DMA 模式下的硬盘读取(伪代码)
// 1. 配置 DMA 控制器
dma_set_source(0x1F0);          // 硬盘数据端口
dma_set_destination(buffer);    // 内存缓冲区
dma_set_length(512);            // 512 字节
dma_start();                    // 启动 DMA

// 2. CPU 可以去做其他事情
do_other_work();

// 3. 等待 DMA 完成中断
wait_for_dma_interrupt();

个人建议:在 Linux 驱动开发中,尽量使用 DMA API(如 dma_map_single、dma_alloc_coherent),而不是自己写 PIO 循环。性能差距是数量级的。

4.3 DMA vs PIO:一张表看懂

对比项 PIO DMA
CPU 参与度 全程参与,每个字节都要 CPU 搬 仅初始化配置,搬运由硬件完成
CPU 利用率 低(CPU 被 I/O 绑死) 高(CPU 可并行处理其他任务)
吞吐量 低(受限于 CPU 指令执行速度) 高(硬件直接搬运,带宽更大)
延迟 低(无额外硬件开销) 略高(DMA 配置和中断有开销)
适用场景 小数据量、低频、简单外设 大数据量、高频、高性能外设
硬件复杂度 低(不需要 DMA 控制器) 高(需要 DMA 控制器支持)

这里要注意一点:DMA 并不是万能的。对于小数据量(比如几十字节),DMA 的配置开销可能比 PIO 还大。我见过有人用 DMA 传 4 个字节,结果配置时间比传输时间还长。嗯,这就有点得不偿失了。

4.4 DMA 的三种传输模式

DMA 控制器也不是傻搬,它有三种工作模式:

  1. 单次传输(Single Transfer):每次 DMA 请求只传输一个数据单元(字节/字),然后释放总线。适合慢速设备。
  2. 块传输(Block Transfer):一次请求传输整个数据块,期间占用总线不放。适合高速设备,但会影响其他设备访问内存。
  3. 请求传输(Demand Transfer):由外设控制传输节奏。外设准备好数据就发请求,DMA 就搬。适合串口这类不定速设备。

我曾经踩过的坑:在某个 FPGA 项目中,我用了块传输模式,结果 DMA 占着总线不放,导致 CPU 无法及时响应中断,系统直接卡死。后来改成请求传输模式,问题解决。记住:块传输虽快,但别让它饿死 CPU。

4.5 知识体系:DMA 与 PIO 的核心逻辑

下面这张图,是我自己总结的 DMA 与 PIO 的核心逻辑对比。你可以看到,PIO 是 CPU 亲自下场,DMA 是 CPU 派了个小弟去干。

DMA vs PIO:数据搬运的核心逻辑 PIO 模式 CPU 外设 内存 读数据 写数据 CPU 亲自搬运每个字节 CPU 占用率 100% DMA 模式 CPU DMA 控制器 外设 内存 配置 读数据 写数据 DMA 控制器负责搬运 CPU 可并行处理其他任务

4.6 实际项目中的选择策略

说了这么多,到底什么时候用 PIO,什么时候用 DMA?我个人的经验是:

  • 数据量 < 64 字节:用 PIO。DMA 的配置开销(设置寄存器、分配缓冲区、处理中断)可能比传输本身还大。
  • 数据量 64 字节 ~ 1KB:看情况。如果 CPU 很闲,PIO 也行;如果 CPU 很忙,果断 DMA。
  • 数据量 > 1KB:必须用 DMA。否则 CPU 会被 I/O 拖死。

避坑指南:我曾经在一个网络驱动中,用 PIO 模式处理 1500 字节的以太网帧。结果网络吞吐只有 200Mbps,CPU 占用 100%。换成 DMA 后,吞吐直接飙到 950Mbps,CPU 占用降到 20%。你看,选对模式,性能翻倍。

好了,DMA 和 PIO 的区别就聊到这。记住一句话:CPU 是用来计算的,不是用来搬砖的。把搬砖的活交给 DMA,你的系统吞吐才能上去。

可可精品资料库