interrupt - dma

09. 中断与 DMA

0. 本章先解决什么问题

上一章已经看到,中断和 DMA 是高效 IO 的核心。本章单独展开,因为它们连接了组成原理和操作系统:

设备事件如何打断 CPU?
CPU 如何保存现场再回来?
大块数据为什么不用 CPU 手动搬?

本章要解决:

  • 中断是什么,为什么需要它?
  • 中断处理程序如何工作?
  • 异常、中断、陷入有什么区别?
  • 中断优先级和屏蔽是什么?
  • DMA 的完整流程是什么?
  • 中断和 DMA 会带来哪些性能和一致性问题?

中断与 DMA 路径

这张图给出一个底层 IO 的核心分工:CPU 负责配置和收尾,设备/DMA 控制器负责大块数据搬运,完成后用中断通知 CPU。这样 CPU 不必一直轮询,也不必逐字节搬数据。

这张图怎么读

读这张图时,把 CPU 从“搬运工”改看成“调度者”:

CPU 配置 DMA
设备和内存之间搬数据
中断通知 CPU 完成
CPU 做收尾和错误处理

图里最重要的两个边界:

边界风险
中断边界控制流被打断,必须保存/恢复现场
DMA 边界设备绕过 CPU cache 读写内存,必须处理可见性和一致性

中断和 DMA 提高 IO 效率,但也把问题从“CPU 忙等”变成了“通知频率、缓冲管理和缓存一致性”。

1. 为什么不能只靠轮询

轮询方式:

while 设备未完成:
CPU 一直检查状态

如果设备很快,轮询还可以接受。
如果设备很慢,CPU 会浪费大量时间。

中断的目标是:

设备没事时,CPU 做别的事。
设备有事时,再通知 CPU。

这让 CPU 和设备可以更好地并发工作。

2. 中断是什么

中断是硬件或系统事件请求 CPU 暂停当前执行流,转去处理特定事件。

简化流程:

CPU 正在执行程序
设备发出中断请求
CPU 在合适时机响应
保存当前现场
跳到中断处理程序
处理中断
恢复现场
继续原来的程序

关键点:

中断改变控制流,但不应该让原程序无故丢失现场。

3. 中断向量和中断处理程序

不同中断需要不同处理逻辑。

系统会有中断向量表:

中断编号 -> 处理程序入口地址

例如:

中断来源处理方向
时钟更新系统时间、触发调度
键盘读取输入
网卡处理收到的数据包
磁盘处理 IO 完成
错误异常处理非法访问、除零等

CPU 收到中断编号后,根据向量表找到处理程序。

4. 保存现场和恢复现场

中断发生时,CPU 原来正在执行某个程序。

为了处理完中断还能回来,必须保存现场:

  • 程序计数器。
  • 状态寄存器。
  • 某些通用寄存器。
  • 必要的栈信息。

处理完后再恢复:

保存现场
-> 处理中断
-> 恢复现场
-> 回到原位置继续

如果现场保存错误,程序会表现得像“莫名其妙被改了状态”。

5. 异常、中断、陷入

这些概念都可能改变控制流,但来源不同。

概念来源例子
外部中断外部设备网卡、磁盘、键盘、时钟
异常当前指令执行导致除零、非法地址、缺页
陷入 trap程序主动请求进入内核系统调用

共同点:

都让 CPU 从普通执行路径转入特殊处理路径。

区别在于触发原因和处理语义。

操作系统大量依赖这些机制:

  • 时钟中断用于调度。
  • 缺页异常用于虚拟内存。
  • trap 用于系统调用。
  • IO 中断用于设备完成通知。

6. 中断优先级和屏蔽

多个中断可能同时发生,需要决定先处理谁。

中断优先级用于排序:

高优先级中断可以先处理
低优先级中断等待

中断屏蔽用于临时禁止某些中断:

进入关键区域
-> 屏蔽部分中断
-> 完成关键操作
-> 恢复中断

但屏蔽中断不能滥用。屏蔽太久会导致:

  • 设备响应延迟。
  • 时钟不准。
  • 系统交互卡顿。
  • 丢事件风险增加。

7. 中断风暴

如果设备频繁产生中断,CPU 可能大部分时间都在处理中断。

这叫中断风暴。

可能原因:

  • 网络包太多。
  • 设备故障。
  • 中断合并策略不合理。
  • 驱动 bug。
  • 系统负载过高。

解决方向:

  • 中断合并。
  • 批量处理。
  • 降低中断频率。
  • 使用轮询和中断混合策略。
  • 优化驱动和缓冲区。

这解释了一个现象:

中断减少了空等,但中断太多也会成为负担。

8. DMA 的基本流程

DMA 让设备或 DMA 控制器直接和内存传输数据,CPU 不负责逐字节搬运。

流程:

CPU 设置 DMA 参数:
源地址
目标地址
数据长度
传输方向

CPU 启动设备
DMA 控制器搬运数据
搬运完成
设备发中断通知 CPU
CPU 做收尾处理

简化图:

设备 <---- DMA ----> 内存
\ /
—中断通知 CPU—/

CPU 从搬运工变成调度者。

手推:一次磁盘读如何经过中断和 DMA

把“读一个磁盘块”拆成硬件动作:

  1. CPU/内核准备一块内存缓冲区
  2. 驱动把缓冲区地址、长度、读命令写给设备控制器
  3. 设备开始从介质读取数据
  4. DMA 把数据写入指定内存区域
  5. 设备完成后发中断
  6. CPU 响应中断,进入处理程序
  7. 驱动确认完成状态,处理错误或成功
  8. 内核标记缓冲区可用,唤醒等待的执行流

这条路径里,CPU 做的是配置、检查和调度,不负责逐字节搬运数据。慢点也不只可能在设备:

阶段可能慢点
提交命令设备队列已满、驱动锁竞争
设备读取介质延迟、控制器排队
DMA 写内存总线竞争、内存带宽
中断通知中断合并、CPU 忙、优先级
唤醒应用调度队列、锁、上层缓冲区

所以“磁盘读慢”在组成原理视角下,不只是磁盘本身慢,而是设备、总线、内存、中断、OS 调度共同组成的路径慢。

9. DMA 和 Cache 一致性

DMA 绕过 CPU 直接读写内存,会带来一致性问题。

场景 1:设备从内存读数据

CPU 修改了缓冲区
数据还在 Cache,没写回内存
设备 DMA 从内存读
设备读到旧数据

场景 2:设备写入内存

设备 DMA 把新数据写入内存
CPU Cache 里还有旧副本
CPU 继续读旧数据

所以系统需要 Cache flush、invalidate 或一致性协议来保证 DMA 缓冲区正确。

学习阶段先记住:

DMA 提高性能,但会让缓存一致性和内存可见性更复杂。

10. 零拷贝和 DMA 的关系

普通数据发送可能涉及多次复制:

磁盘 -> 内核缓冲区 -> 用户缓冲区 -> 内核 socket 缓冲区 -> 网卡

零拷贝优化会尽量减少用户态和内核态之间的复制,让数据路径更短:

磁盘/页缓存 -> 网卡

DMA 在其中负责设备和内存之间的高效传输。

核心思想:

如果程序不需要修改数据,就不要让数据来回穿过程序内存。

11. 中断和调度

时钟中断是操作系统调度的重要基础。

没有时钟中断,一个程序如果一直运行,系统很难定期夺回 CPU。

时钟中断让系统有机会:

  • 更新时间。
  • 检查当前任务运行时间。
  • 决定是否切换任务。
  • 处理定时器。

这连接到操作系统里的:

时间片
进程调度
上下文切换
定时器

12. 联系实际:为什么网络高负载会让 CPU 忙

高网络流量下,CPU 可能忙在:

  1. 网卡频繁产生中断。
  2. 操作系统处理大量包。
  3. DMA 缓冲区管理。
  4. 协议栈解析。
  5. 数据复制。
  6. 应用读取不及时导致队列堆积。

优化方向可能是:

  • 中断合并。
  • 批量收包。
  • 增大或调整缓冲区。
  • 减少复制。
  • 分散到多个核心。
  • 应用更快消费。

这说明 IO 性能不是“网速”一个指标决定的,它也依赖 CPU、中断、DMA、缓存和内存带宽。

排障卡:CPU 忙在 IO 上时看中断、复制和消费速度

网络或磁盘负载高时,CPU 忙不一定是在跑业务计算。可以按这张表拆:

现象可能机制方向
中断计数暴涨设备频繁通知 CPU中断合并、批量处理、分散到多核
CPU 花在内核态协议栈、驱动、缓冲区管理看包量、系统调用、内核队列
内存带宽压力高大量数据复制减少复制、批量、零拷贝路径
应用读不及时内核队列堆积提高消费速度、背压、线程/事件模型
DMA 后数据异常Cache 可见性问题flush/invalidate、一致性边界

一个很实用的判断是:

如果吞吐上升时,业务 CPU 没明显增加,但系统 CPU 和中断处理明显增加,
问题可能在设备通知、内核协议栈、拷贝和缓冲区管理,而不是业务算法。

中断和 DMA 是性能工具,也会引入新的成本:通知太频繁会拖垮 CPU,绕过 Cache 的数据路径要处理一致性。

机制深挖:中断风暴不是“事件多”这么简单

中断风暴的核心是:

CPU 被设备通知路径占用太多,
真正留给应用和协议处理的时间变少。

一次事件的固定成本包括:

保存现场
跳到中断处理路径
读取设备状态
确认或清除中断
调度后续处理
恢复现场

如果每个包、每个小 IO、每个设备状态变化都触发一次通知,CPU 会不断进入这些固定路径。于是系统可能出现:

现象解释
系统 CPU 高大量时间花在内核/中断路径
应用吞吐不上去应用拿到 CPU 的机会减少
延迟长尾升高包或请求在后续队列里等待
丢包或设备队列满CPU 来不及消费通知后的工作

这也是为什么高性能 IO 会使用:

中断合并
批量轮询
多队列
每核队列
减少小包

它们的共同目标是摊薄“每次通知”的固定成本。

边界条件:屏蔽中断能保护临界区,也可能放大延迟

某些底层路径会短时间屏蔽中断,防止关键状态更新被打断。但屏蔽时间过长会带来副作用:

代码块JAVA · 3 行收起展开
设备事件已经发生;
CPU 暂时不响应;
队列继续积压;

响应延迟上升。

所以中断控制有一个硬约束:

需要原子保护的区域越短越好。

如果为了简单粗暴地避免并发问题而长时间屏蔽中断,就可能把正确性问题变成实时性和吞吐问题。

这和锁很像:

机制保护什么过度使用的代价
共享数据不变量阻塞、死锁、争用
屏蔽中断关键硬件/内核状态响应延迟、事件积压

底层系统设计的难点就在这里:既要保护状态一致性,又不能让外部事件等待太久。

13. 学完本章你能解决什么问题

学完这一章,你应该能解决或开始分析这些问题:

  1. 为什么中断比单纯轮询更高效?
  2. 中断发生时 CPU 如何保存和恢复现场?
  3. 外部中断、异常、trap 有什么区别?
  4. 中断优先级和屏蔽为什么必要?
  5. 中断风暴为什么会拖垮系统?
  6. DMA 如何减少 CPU 搬运大块数据的负担?
  7. DMA 为什么会带来 Cache 一致性问题?
  8. 零拷贝为什么能提升 IO 性能?
  9. 时钟中断为什么和操作系统调度有关?

中断让设备能通知 CPU,DMA 让设备能高效搬数据。它们是硬件 IO 和操作系统之间最重要的桥。

延伸阅读