中断与异常
中断(Interrupt)是硬件向CPU发出的"紧急通知",异常(Exception)是程序执行中出现的"意外事件"——它们让CPU能够暂停当前工作,处理紧急任务,再回来继续
CPU 怎么知道该”停”了?
CPU 正在执行你的程序,这时:
- 你按下了键盘上的一个键
- 网络接口卡收到了一条数据
- 硬盘完成了数据读取
- 程序试图除以 0
- 定时器到期了
CPU 怎么知道这些事情发生了?它怎么停下当前工作去处理它们,再回来继续?
中断(Interrupt) 和 异常(Exception) 就是答案。
类比:你在写作业
- 中断 = 妈妈敲门说”吃饭了”——你暂停写作业(保存进度),去吃饭(处理中断),吃完回来继续写(恢复进度)
- 异常 = 你发现作业本上用错了公式——你停下来擦掉重写(处理异常),再继续后面的题
- 陷阱(Trap) = 你主动举手问老师”这道题怎么做”——你故意停下来等指导
中断 vs 异常 vs 陷阱
| 类型 | 触发者 | 触发原因 | 是否可预测 |
|---|---|---|---|
| 中断(Interrupt) | 外部硬件设备 | 键盘输入、网络数据到达、定时器 | ❌ 完全不可预测 |
| 异常(Exception) | CPU 内部 | 除零、缺页、非法指令 | ⚠️ 不常见但可能 |
| 陷阱(Trap / Syscall) | 程序主动触发 | 系统调用(int 0x80、ecall) | ✅ 程序故意为之 |
具体分类
中断(硬件产生):
IRQ(Interrupt Request)——硬件设备通过中断控制器向 CPU 发信号
- 键盘按下 → 键盘控制器 → IRQ1 → CPU
- 硬盘就绪 → 硬盘控制器 → IRQ14 → CPU
- 定时器到期 → PIT/APIC → IRQ0 → CPU
异常(CPU 内部检测):
- 故障(Fault):除零错误、缺页(可恢复)
- 陷阱(Trap):调试断点、系统调用(有意为之)
- 终止(Abort):严重硬件错误(不可恢复)
中断的处理流程
一个完整的中断处理分为 6 步:
┌─────────┐ ① 中断请求 ┌──────────┐
│ 硬件设备 │ ─────────────────→ │ 中断控制器 │
│(键盘等)│ │(PIC/APIC)│
└─────────┘ └─────┬────┘
│ ② 中断信号
↓
┌─────────────────────────────────────────────┐
│ CPU │
│ ┌──────────────────────────────────────┐ │
│ │ ③ 当前指令执行完毕 │ │
│ │ ④ 保存上下文(PC、状态寄存器、...) │ │
│ │ ⑤ 跳转到中断处理程序(ISR) │ │
│ │ ⑥ 处理完 → 恢复上下文 → 继续原程序 │ │
│ └──────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
① 中断请求(IRQ)
硬件设备通过**中断请求线(IRQ)**向 CPU 发送信号。PC 中每个设备有固定的 IRQ 号:
IRQ0: 定时器
IRQ1: 键盘
IRQ2: 级联中断控制器
IRQ3: COM2(串口)
IRQ4: COM1(串口)
IRQ6: 软盘控制器
IRQ8: 实时时钟
IRQ12: PS/2 鼠标
IRQ14: 主 IDE 通道(硬盘)
② 中断控制器
设备不直接连接 CPU——它们通过中断控制器集中管理:
传统方案(PIC,8259A):
键盘 → PIC → CPU(INT 引脚)
定时器 → PIC → CPU
硬盘 → PIC → CPU
现代方案(APIC,高级可编程中断控制器):
每个 CPU 核心有自己的 Local APIC,支持更多中断、多核分发
③ 当前指令完成
CPU 不会在执行指令的中间停下来处理中断——它会等当前指令执行完。这是为了保持指令的原子性。
④ 保存上下文
CPU 自动把关键状态保存到栈上:
中断发生前的栈: 中断发生后的栈:
┌──────────────┐
│ 程序状态字 │ ← 中断自动压入
├──────────────┤
│ 返回地址(PC)│ ← 中断自动压入
├──────────────┤ ← SP(新栈顶)
│ 通用寄存器 │ ← ISR 自己压入(或 CPU)
│ ... │
└──────────────┘
🔑 保存上下文是中断处理最关键的一步——它保证了中断处理完成后,程序能无缝恢复,就像什么都没发生过。
⑤ 中断向量表(IVT,Interrupt Vector Table)
CPU 根据中断号查表,找到对应的**中断处理程序(ISR, Interrupt Service Routine)**地址:
中断向量表(放在内存固定位置):
地址 内容
IVT[0] → 除零错误处理程序
IVT[1] → 调试异常处理程序
IVT[2] → 非屏蔽中断处理程序
IVT[3] → 断点处理程序
...
IVT[32] → IRQ0(定时器)处理程序
IVT[33] → IRQ1(键盘)处理程序
...
IVT[128] → 系统调用处理程序(Linux int 0x80)
; x86 中中断的处理流程(简化)
; 中断号 N 到来时,CPU 自动做:
; 1. PUSH EFLAGS ; 保存状态寄存器
; 2. PUSH CS ; 保存代码段
; 3. PUSH EIP ; 保存返回地址(PC)
; 4. EIP = IVT[N] ; 跳转到中断处理程序
; 键盘中断(IRQ1)的处理程序
keyboard_isr:
PUSH AX ; 保存更多寄存器
PUSH BX
IN AL, 0x60 ; 从键盘端口读扫描码
MOV [key_buffer], AL ; 存入缓冲区
MOV AL, 0x20 ; 发送 EOI(End of Interrupt)信号
OUT 0x20, AL ; 通知中断控制器:处理完了
POP BX ; 恢复寄存器
POP AX
IRET ; 中断返回(恢复 EFLAGS, CS, EIP)
⑥ 中断返回
IRET(Interrupt Return)指令恢复之前压入的 PC、状态寄存器等——CPU 从中断处继续执行,完全不知道发生过中断。
ISR 执行前: ISR 执行后:
用户程序正在运行 用户程序继续运行
│ ↑
└──→ 中断到来 ──→ ISR ────┘ (IRET)
中断嵌套
高优先级的中断可以打断低优先级的中断处理程序:
时间 →
程序: ██████████████████████████████████
↓ 中断 A(低优先级)
ISR A: ████████
↓ 中断 B(高优先级)→ 可以打断 ISR A!
ISR B: ████
↑ 返回
ISR A: ████████████████
↑ 返回
程序: ██████████████████████████████████
中断嵌套需要:
- 保存现场(ISR B 要保存 ISR A 的上下文)
- 重新开启中断(ISR A 要允许更高优先级的中断)
- 栈空间(每层嵌套消耗栈空间)
⚠️ 中断风暴:如果设备频繁产生中断,CPU 可能花所有时间在 ISR 之间切换,几乎没有时间执行用户程序。这就是为什么高频率设备(网络卡、硬盘)通常使用 DMA 而不是中断。
异常的处理
异常是 CPU 内部检测到的,处理流程和中断类似,但来源不同:
故障(Fault)—— 可恢复
在故障指令执行前触发,处理完成后重新执行该指令:
; 除零故障
DIV R1, R2 ; R2 = 0?
; CPU 检测到除零 → 触发异常 #0
; → 跳转到除零处理程序
; → 默认行为:终止程序(信号 SIGFPE)
用户程序: DIV R1, R2 ← 执行前触发异常
↑
│ 重新执行(如果修复了问题)
异常处理程序
│
↓ 报告错误(通常无法修复)
程序终止
另一个可恢复的例子——缺页故障(Page Fault):
LOAD R0, [addr] ; addr 不在内存中?
; CPU 触发缺页异常 → OS 从磁盘加载
; → 恢复后重新执行 LOAD → 这次成功了
陷阱(Trap)—— 程序主动
程序主动触发的”中断”——最典型的是系统调用:
; Linux 系统调用(32 位)
MOV EAX, #4 ; 系统调用号(write)
MOV EBX, #1 ; 参数1(stdout)
MOV ECX, msg ; 参数2(字符串地址)
MOV EDX, #12 ; 参数3(长度)
INT 0x80 ; 触发陷阱 → CPU 进入内核态
; 执行系统调用 → 返回用户态
; 下一条指令是 INT 0x80 后面那条
陷阱执行完后,返回到触发陷阱的指令的下一条(而不是重新执行触发指令)。
终止(Abort)—— 不可恢复
严重的硬件错误,通常是无法恢复的:
- 内存校验错误(ECC 无法纠正)
- 总线错误
- 缓存一致性协议崩溃
→ CPU 通常直接停机或重启
中断优先级
不同中断的紧急程度不同,CPU 优先响应高优先级的中断:
| 优先级 | 中断类型 | 例子 |
|---|---|---|
| 最高 | CPU 内部异常 | 除零、页故障 |
| ↑ | 非屏蔽中断(NMI) | 内存校验错、看门狗 |
| 高优先级硬件中断 | 定时器、硬盘 DMA | |
| 中优先级硬件中断 | 网络卡、声卡 | |
| 最低 | 低优先级硬件中断 | 键盘、鼠标 |
中断控制器根据优先级仲裁:当多个中断同时到达时,优先响应高优先级;低优先级中断等待。
系统调用:用户态到内核态的桥梁
系统调用(syscall)是中断/陷阱最重要的应用之一——它让用户程序能请求操作系统服务:
用户态(Ring 3) 内核态(Ring 0)
┌──────────────┐ ┌──────────────┐
│ 应用程序 │ │ 操作系统内核 │
│ │ │ │
│ printf() │ │ sys_write() │
│ ↓ │ │ 处理 I/O │
│ 封装库函数 │ │ 设备驱动 │
│ ↓ │ │ 返回结果 │
│ INT 0x80 ─┼───── 陷阱 ──→ │ │
│ │←──── 返回 ──── ─┤ │
│ 继续执行 │ │ │
└──────────────┘ └──────────────┘
系统调用的核心作用:提升特权级——用户程序不能直接访问硬件,但通过系统调用陷入内核态,由 OS 代为执行。
中断在现代操作系统中的角色
| 场景 | 中断的作用 |
|---|---|
| 进程调度 | 定时器中断 → OS 检查是否需要切换进程 |
| 输入/输出 | 键盘/鼠标中断 → 读取输入数据 |
| 网络通信 | 网卡中断 → 数据包到达通知 |
| 磁盘读写 | 硬盘中断 → 数据就绪通知 |
| 电源管理 | 休眠/唤醒事件 |
| 硬件错误 | 报告内存错误、过热等 |
小结
中断和异常是 CPU 响应”意外事件”的核心机制:
| 概念 | 要点 |
|---|---|
| 中断(Interrupt) | 外部硬件发起,异步,与当前指令无关 |
| 异常(Exception) | CPU 内部检测,同步,由当前指令引起 |
| 陷阱(Trap / Syscall) | 程序主动触发,用于请求 OS 服务 |
| 中断向量表(IVT) | 中断号 → 处理程序地址的映射表 |
| 上下文保存 | 中断前保存 PC 和状态寄存器,IRET 恢复 |
| 中断嵌套 | 高优先级中断可打断低优先级 ISR |
为什么这很重要? 中断是 CPU 与外部世界交互的主要方式——没有中断,CPU 要么只能不停地轮询设备(浪费),要么无法响应外部事件。中断让 CPU 可以”在需要时被通知”,这是现代操作系统能够实现多任务、交互性和响应的基础。
接下来,你将学习 CPU 如何与外设进行更高效的数据交换——I/O 接口与 DMA:I/O 接口(DMA、内存映射 I/O)。