输入输出与总线 — 中断、DMA,以及 PCIe
一句话总结
与设备通信可分为三层:何时通知(轮询或中断)、由谁搬运(CPU 或 DMA)、经过哪里(总线与互连)。
为什么需要了解这些
CPU 以纳秒运行,磁盘与网卡则以微秒到毫秒运行。若不隐藏这种速度差,CPU 会把时间都花在等待设备上。I/O 设计史就是不断回答如何隐藏这段差距。
工作原理
轮询与中断。 轮询让 CPU 不断读取设备状态寄存器,简单且延迟低,但 CPU 无法做其他工作。中断则由设备就绪时通知 CPU,CPU 可先做别的事,但每次中断都要保存上下文并跳转到处理程序。每秒数十万数据包时,中断反而会使系统瘫痪(中断风暴),因此 Linux NAPI 等开始用中断、繁忙时转为轮询的混合方式已成为标准。
DMA。 若数据逐字经过 CPU 寄存器搬运,CPU 会沦为搬运工。DMA 控制器直接在设备和内存之间移动数据,全部完成后只触发一次中断。现代系统的大批量传输无一例外使用 DMA。
MMIO 与端口 I/O。 把设备寄存器映射进内存地址空间后,可用普通加载/存储访问(MMIO)。语法虽与内存相同,成本却完全不同。 MMIO 读取属于必须等待响应的非发布事务,PCIe 往返时间会直接成为执行时间,且不能缓存。驱动若在循环中读取设备寄存器,该循环并非计算,而是在反复进行 I/O 往返。
PCIe。 带宽由通道数和代际决定。每代每通道速度大约翻倍,并可组合成 x1/x4/x8/x16。GPU 插在 x4 而非 x16 插槽中时,计算能力不变,但数据供给降为四分之一。多 GPU 训练使用 NVLink 等专用互连也是因为卡间 PCIe 通信会成为瓶颈。
实际工作中的表现
家庭实验室搭建 GPU 节点时,最常见错误是忽略插槽带宽。主板第二、第三插槽常只有电气 x4;插三张卡后,两张会悄悄以四分之一带宽运行。训练仍能进行,但每步耗时异常。养成用 lspci -vv 检查协商链路宽度的习惯可以节省大量时间。
用数字定位瓶颈
记住带宽的数量级即可建立直觉。
| 路径 | 大致带宽 |
|---|---|
| CPU ↔ L1 缓存 | 1 TB/s 以上 |
| CPU ↔ 内存(DDR5) | 50~100 GB/s |
| PCIe 4.0 ×16 | 32 GB/s |
| PCIe 4.0 ×4(单块 NVMe) | 8 GB/s |
| 10 GbE 网络 | 1.25 GB/s |
| SATA III | 0.6 GB/s |
使用多块 GPU 时,PCIe 会成为瓶颈。 32GB/s 甚至不到内存带宽的一半,因此频繁在卡间传输数据的模型并行会很慢,这正是 NVLink 存在的原因。
还要检查通道数。即使插入 ×16 插槽,主板若只连接 ×4,带宽仍只有四分之一。
lspci -vv -s <슬롯> | grep -E 'LnkCap|LnkSta'
LnkCap: Speed 16GT/s, Width x16 ← 장치가 할 수 있는 것
LnkSta: Speed 16GT/s, Width x8 ← 실제로 연결된 것 ← 절반이다
两行不同时,说明插槽或分叉(bifurcation)配置有问题。GPU 性能只有预期的一半时,应先查这里。
中断与轮询
设备完成工作后通知 CPU 有两种方式。
- 中断——设备呼叫 CPU。等待延迟短,但有上下文切换成本,每秒数十万次时成本很高。
- 轮询——CPU 持续检查。吞吐量高,但空闲时也占用 CPU。
高速网络和 NVMe 会混合使用两者。 Linux NAPI 在数据包激增时关闭中断并切换为轮询,因此 si(softirq) 使用率高通常表示网络繁忙。
ethtool -c eth0 的中断合并(coalescing)设置可调节这种平衡。增大该值会提高吞吐量,也会增加延迟。
DMA 的作用
设备不经过 CPU,直接读写内存。否则从磁盘读取 1GB 时,CPU 必须亲自搬运全部 1GB。
由此产生了零拷贝。把文件发到套接字时,普通路径是磁盘 → 内核缓冲区 → 用户缓冲区 → 内核套接字缓冲区 → NIC,共复制四次;sendfile() 或 splice() 会绕过用户空间。这是静态文件服务器速度快的原因,也是 nginx 的 sendfile on 所做的事。
后续测验将确认什么
确认你能解释同一条 mov 指令为何访问某些地址只需纳秒,而访问另一些地址却可能耗费毫秒。