高级 #hardware#cpu#interrupts#exceptions

中断与异常

中断(Interrupt)是硬件向CPU发出的"紧急通知",异常(Exception)是程序执行中出现的"意外事件"——它们让CPU能够暂停当前工作,处理紧急任务,再回来继续

CPU 怎么知道该”停”了?

CPU 正在执行你的程序,这时:

  • 你按下了键盘上的一个键
  • 网络接口卡收到了一条数据
  • 硬盘完成了数据读取
  • 程序试图除以 0
  • 定时器到期了

CPU 怎么知道这些事情发生了?它怎么停下当前工作去处理它们,再回来继续?

中断(Interrupt)异常(Exception) 就是答案。

类比:你在写作业

  • 中断 = 妈妈敲门说”吃饭了”——你暂停写作业(保存进度),去吃饭(处理中断),吃完回来继续写(恢复进度)
  • 异常 = 你发现作业本上用错了公式——你停下来擦掉重写(处理异常),再继续后面的题
  • 陷阱(Trap) = 你主动举手问老师”这道题怎么做”——你故意停下来等指导

中断 vs 异常 vs 陷阱

类型触发者触发原因是否可预测
中断(Interrupt)外部硬件设备键盘输入、网络数据到达、定时器❌ 完全不可预测
异常(Exception)CPU 内部除零、缺页、非法指令⚠️ 不常见但可能
陷阱(Trap / Syscall)程序主动触发系统调用(int 0x80ecall✅ 程序故意为之

具体分类

中断(硬件产生):

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)