中断机制:操作系统如何响应外部事件
一句话结论(30s)
中断让 CPU 从「主动轮询」变成「事件驱动」。因为外设触发的中断和 CPU 自身指令触发的异常(陷阱/故障/终止)都通过中断向量表统一处理,所以缺页中断(malloc 延迟分配的兜底)和系统调用(进内核的唯一正规入口)本质上是同一种机制的两个特例。
核心原理(2min)
- 分类:外部中断(INTR 可屏蔽 / NMI 不可屏蔽)vs 内部异常(陷阱 / 故障 / 终止)
- 基本流程:中断信号 → 保存现场(PC、标志寄存器入栈)→ 查中断向量表 → 执行处理程序 →
iret返回 - 缺页中断:
malloc只分配虚拟空间,首次访问 → MMU 发现页表无效 → 分配物理页 → 更新页表 → 重执行该指令 - 系统调用:
int 0x80(32位)vssyscall(64位,跳过 IDT 查表,快约 1.5 倍) - 为什么需要中断:轮询浪费 CPU,中断实现事件驱动,CPU 空闲时可执行其他任务或 idle 省电
底层深入(5-10min)
分类
三种中断类型:
外部中断(硬件):
- 可屏蔽中断 (INTR): 键盘、网卡、定时器
- 不可屏蔽中断 (NMI): 硬件故障、掉电警告
内部中断(CPU 指令触发):
- 陷阱: 系统调用(int 0x80 / syscall)
- 故障: 缺页、除零
- 终止: 硬件错误(不可恢复)
中断的基本流程
1. 外设/CPU 发中断信号
2. CPU 完成当前指令 → 保存现场(PC、标志寄存器入栈)
3. 根据中断向量号查中断向量表 → 找到中断处理程序入口
4. 执行中断处理程序
5. 恢复现场 → iret 返回原任务
缺页中断:延迟分配的核心
进程 malloc(1GB) 时,内核只分配虚拟地址空间(更新 VMA 结构),不分配物理内存。第一次访问该地址 → MMU 查页表发现无效 → 触发缺页中断:
缺页中断处理:
1. 检查访问地址是否在 VMA 范围内(合法但未分配)
2. 分配物理页
3. 更新页表(填入物理页框号 + 权限位)
4. iret 返回应用 → 重新执行触发缺页的指令
应用层完全感知不到缺页中断——指令被短暂中断,分配物理页后立即恢复执行。malloc + lazy allocation = 双重延迟让内存使用极其高效。
思考:为什么 malloc 只分配虚拟空间、不立刻给物理页?因为一次
malloc(1GB)很可能大部分内存根本不会被用到,提前全部分配物理页是巨大浪费。内核赌的是”先记账,用时再掏钱”——第一次真正访问才通过缺页中断按需分配。这种”延迟分配”把”申请”和”使用”解耦,是内存高效的关键。
系统调用(陷阱):从用户态进入内核的唯一正规入口
// x86 32位: int 0x80
mov eax, 1 // 系统调用号 (1 = exit)
int 0x80 // 触发陷阱,切换到内核态
// x86 64位: syscall 指令(更快)
mov rax, 60 // 系统调用号 (60 = exit)
syscall
int 0x80 通过中断描述符表跳转到内核的系统调用入口。syscall 是 AMD/Intel 的快速系统调用指令——跳过了中断描述符表的查找,直接切换到内核例程,比 int 0x80 快约 1.5 倍。
思考:为什么
syscall比int 0x80快?因为int 0x80走的是通用中断流程——要查 IDT、做权限和栈切换的全套检查;而syscall是专用快速通道,硬件直接把入口地址和状态准备好,跳过 IDT 查表这一步。省掉一次查表和多余检查,就是那约 1.5 倍的来源。这也是为什么现代 64 位程序都用syscall而非int 0x80。
为什么需要中断
没有中断 → 只能用轮询 → CPU 反复检查”键盘有输入吗?”→ 即使没有人按键,CPU 也在空转。中断让 CPU 从”主动轮询”变成”事件驱动”——只有事件发生才被通知,CPU 在不需要处理外设时可以执行其他任务(或 idle 省电)。
思考:那是不是”中断永远比轮询好”?不一定。轮询虽然浪费 CPU,但响应是确定性的、没有保存/恢复现场的开销。在”事件极其密集”的场景(比如网卡每秒百万级小包),频繁中断反而被中断处理开销淹没,于是又回到”轮询”(如 NAPI 先中断后轮询)。取舍核心是”事件频率 × 单次开销”——频率低用中断,频率极高就混合轮询。
章末提问
追问 1:malloc 之后立刻访问这块内存,底层会发生什么?
回答思路:结论先行——会触发一次缺页中断,内核按需分配物理页。因为 malloc 只更新 VMA 分配了虚拟地址空间,并没有真正给物理页;第一次访问时 MMU 查页表发现无效(Present=0),触发缺页中断,内核检查地址合法后分配物理页、更新页表,然后重执行那条访存指令,应用层全程无感。
追问 2:为什么 syscall 比 int 0x80 快?
回答思路:结论先行——因为 syscall 跳过了 IDT 查表和多余检查。int 0x80 走通用中断路径,要查中断描述符表、做完整的权限/栈切换检查;而 syscall 是 CPU 提供的专用快速指令,硬件直接把入口和状态备好,少一次查表和检查,所以快约 1.5 倍。
追问 3:中断和轮询各有什么优劣?什么时候该用哪个?
回答思路:结论先行——低频率事件用中断省 CPU,极高频率事件用轮询(或混合)避免中断开销淹没。因为中断是事件驱动、不空转但每次要保存/恢复现场;轮询响应确定、无现场开销但会空转浪费 CPU。当事件密集到中断处理开销超过空转代价时(如网卡百万级小包),就改用 NAPI 这类”先中断后轮询”的混合方案。