中断处理 - 理论讲解
学习目标
本章你将学会:
- 理解中断的概念和工作原理
- 配置中断描述符表(IDT)
- 编写中断服务程序(ISR)
- 重映射可编程中断控制器(PIC)
- 处理CPU异常和硬件中断
1. 什么是中断?
中断是CPU暂停当前执行流程、转去处理某个事件的机制。中断是操作系统最核心的机制之一。
中断的来源
┌───────────────────────────────────────────────┐
│ 中断来源 │
├─────────────┬──────────────┬──────────────────┤
│ CPU异常 │ 硬件中断 │ 软件中断 │
│ (向量 0-31) │ (向量 32-47) │ (INT指令) │
├─────────────┼──────────────┼──────────────────┤
│ 除零错误 │ 时钟(IRQ0) │ 系统调用 │
│ 页错误 │ 键盘(IRQ1) │ 调试断点 │
│ 通用保护错误 │ 硬盘(IRQ14) │ ... │
└─────────────┴──────────────┴──────────────────┘
中断处理流程
正常执行 → 中断发生 → 保存现场 → 查IDT → 执行ISR → 恢复现场 → 继续执行
详细步骤:
- 保存现场: CPU自动压入
EFLAGS、CS、EIP(有时还有错误码) - 查找处理程序: 根据中断向量号查IDT
- 执行ISR: 跳转到中断服务程序
- 发送EOI: 对于硬件中断,告知PIC处理完毕
- 恢复现场:
IRET指令恢复之前的状态
2. 中断描述符表(IDT)
IDT是保护模式下的中断向量表,每个条目描述一个中断处理程序。
IDT条目结构(8字节)
位: 0 15 16 31
┌─────────────────┬────────────────┐
│ 基址低16位 │ 段选择子 │ 字节 0-3
├─────────────────┼────────────────┤
│ 保留(全0) │ 标志位 │ 字节 4-5
├─────────────────┼────────────────┤
│ 基址高16位 │ │ 字节 6-7
└─────────────────┴────────────────┘
标志位详解
7 0
┌───┬──┬──┬─────────────────┐
│ P │DPL│ 0│ Gate Type │
└───┴──┴──┴─────────────────┘
P = 1 (Present, 条目有效)
DPL = 00 (特权级0)
Gate = 1110 (32位中断门)
组合: 1 00 0 1110 = 0x8E
C语言表示
struct idt_entry {
uint16_t base_low; // 处理程序地址低16位
uint16_t selector; // 代码段选择子
uint8_t always0; // 保留
uint8_t flags; // 类型和属性
uint16_t base_high; // 处理程序地址高16位
} __attribute__((packed));3. CPU异常(向量 0-31)
Intel预定义了32个CPU异常:
| 向量 | 名称 | 类型 | 有错误码? |
|---|---|---|---|
| 0 | 除零错误 | Fault | 无 |
| 1 | 调试异常 | Fault/Trap | 无 |
| 6 | 无效操作码 | Fault | 无 |
| 8 | 双重错误 | Abort | 有(总是0) |
| 10 | 无效TSS | Fault | 有 |
| 11 | 段不存在 | Fault | 有 |
| 12 | 栈段错误 | Fault | 有 |
| 13 | 通用保护错误 | Fault | 有 |
| 14 | 页错误 | Fault | 有 |
异常类型
- Fault: 可以修复,修复后重新执行造成错误的指令
- Trap: 报告完毕后执行下一条指令(如断点)
- Abort: 严重错误,通常无法恢复
4. 可编程中断控制器(PIC)
8259A PIC
PC系统中有两个PIC芯片级联使用:
主PIC (Master) 从PIC (Slave)
端口 0x20/0x21 端口 0xA0/0xA1
┌──────────┐ ┌──────────┐
IRQ0 ────│ │ │ │──── IRQ8
IRQ1 ────│ │ │ │──── IRQ9
IRQ2 ────│ ◄─────┼──────────────┤ │──── IRQ10
IRQ3 ────│ │ │ │──── IRQ11
IRQ4 ────│ │ │ │──── IRQ12
IRQ5 ────│ │ │ │──── IRQ13
IRQ6 ────│ │ │ │──── IRQ14
IRQ7 ────│ │ │ │──── IRQ15
└──────────┘ └──────────┘
为什么要重映射PIC?
BIOS默认将 IRQ 0-7 映射到向量 8-15,与CPU异常冲突!
重映射方案:
- IRQ 0-7 → 向量 32-39(主PIC)
- IRQ 8-15 → 向量 40-47(从PIC)
重映射代码解析
void pic_remap(void) {
// ICW1: 开始初始化(级联模式 + 需要ICW4)
outb(0x20, 0x11); // 主PIC
outb(0xA0, 0x11); // 从PIC
// ICW2: 设置基础向量号
outb(0x21, 0x20); // 主PIC: 向量32开始
outb(0xA1, 0x28); // 从PIC: 向量40开始
// ICW3: 主从连接配置
outb(0x21, 0x04); // 主PIC: 第2号引脚连从PIC
outb(0xA1, 0x02); // 从PIC: 连到主PIC的IRQ2
// ICW4: 8086模式
outb(0x21, 0x01);
outb(0xA1, 0x01);
// 清除中断掩码(启用所有IRQ)
outb(0x21, 0x00);
outb(0xA1, 0x00);
}5. 中断服务程序的编写
汇编入口(interrupt.asm)
为什么需要汇编?因为中断处理程序需要:
- 统一栈布局(为有/无错误码的中断创建一致的栈帧)
- 保存所有寄存器(
pusha) - 切换到内核数据段
- 调用C函数
- 恢复现场并
iret
; 无错误码的ISR宏
%macro ISR_NOERRCODE 1
global isr%1
isr%1:
push dword 0 ; 伪错误码(统一栈布局)
push dword %1 ; 中断号
jmp isr_common_stub
%endmacro
; 有错误码的ISR宏(CPU已压入错误码)
%macro ISR_ERRCODE 1
global isr%1
isr%1:
push dword %1 ; 中断号
jmp isr_common_stub
%endmacro栈帧布局
中断发生后的完整栈布局:
高地址
┌──────────────┐
│ EFLAGS │ ← CPU自动压入
├──────────────┤
│ CS │ ← CPU自动压入
├──────────────┤
│ EIP │ ← CPU自动压入
├──────────────┤
│ 错误码/0 │ ← CPU或我们压入
├──────────────┤
│ 中断号 │ ← 我们压入
├──────────────┤
│ EAX~EDI │ ← pusha
├──────────────┤
│ DS │ ← 我们保存
├──────────────┤ ← ESP → 作为参数传给C函数
低地址
这个栈帧对应C语言中的 registers_t 结构体!
6. EOI(中断结束信号)
处理硬件中断后,必须发送EOI告知PIC:
void irq_handler(registers_t *regs) {
// 如果来自从PIC(IRQ8-15),先给从PIC发EOI
if (regs->int_no >= 40) {
outb(0xA0, 0x20);
}
// 总是给主PIC发EOI
outb(0x20, 0x20);
// 调用注册的处理程序
if (handlers[regs->int_no]) {
handlers[regs->int_no](regs);
}
}忘记发EOI会导致该IRQ线路永远被屏蔽,不再收到中断!
7. 关键概念总结
| 概念 | 说明 |
|---|---|
| IDT | 中断描述符表,256个条目,每个8字节 |
| ISR | 中断服务程序,处理中断的代码 |
| PIC | 可编程中断控制器,管理硬件中断 |
| EOI | 中断结束信号,告知PIC处理完毕 |
| IRET | 中断返回指令,恢复EFLAGS/CS/EIP |
| Fault | 可恢复异常,重新执行出错指令 |
| Trap | 执行下一条指令(如断点) |
下一步
中断系统建立后,我们就能处理硬件事件了。下一章将实现键盘驱动——第一个真正的设备驱动程序!