3. 中断与异常基础:x86架构的中断描述符表(IDT)、中断门与陷阱门、异常处理流程

中断与异常是操作系统响应硬件事件与软件错误的根本机制。在 x86 架构中,CPU 通过中断描述符表(IDT)将中断向量号映射到对应的处理程序入口。理解 IDT 的结构、门描述符的类型以及异常处理流程,是分析系统调用全链路的前提。

3.1 中断描述符表(IDT)

IDT 是一个由 256 个 8 字节描述符组成的数组,每个描述符对应一个中断向量号(0~255)。CPU 在响应中断或异常时,根据向量号从 IDT 中取出对应的门描述符,解析出处理程序的段选择子与偏移量,进而跳转执行。

关键寄存器:IDTR(Interrupt Descriptor Table Register)存储 IDT 的基地址与限长。使用 lidt 指令加载。
向量号分配:0~31 为 Intel 保留的异常与 NMI;32~255 为用户定义的中断(包括系统调用使用的 int 0x80 或 syscall 指令)。
IDT 结构示意图(x86-64) IDT 基地址(由 IDTR 指定) 向量 0 向量 1 向量 2 ... 向量 128 ... 向量 255 系统调用入口(int 0x80) 每个描述符 8 字节:段选择子 + 偏移量 + 门类型 + DPL + P 位

3.2 中断门与陷阱门

IDT 中的描述符类型决定了 CPU 在跳转时的行为差异。最常用的是中断门(Interrupt Gate)和陷阱门(Trap Gate)。两者都包含目标代码段选择子、偏移量、DPL 和存在位,但关键区别在于对 IF(Interrupt Flag) 的处理。

特性 中断门(Interrupt Gate) 陷阱门(Trap Gate)
IF 标志 进入时自动清除 IF(禁止嵌套中断) 不修改 IF(允许嵌套中断)
典型用途 硬件中断处理(如时钟、键盘) 异常处理、系统调用(int 0x80)
返回指令 iret / iretq iret / iretq
DPL 检查 CPL ≤ DPL 时允许触发 CPL ≤ DPL 时允许触发
注意:Linux 内核在 x86 上使用陷阱门实现系统调用(int 0x80),因为系统调用处理期间需要保持中断使能,避免丢失硬件中断。对于时钟中断等硬件中断,则使用中断门自动关闭中断,防止重入。
中断门 vs 陷阱门:CPU 行为对比 中断门触发 清除 IF → 禁止嵌套中断 执行处理程序 → iret 恢复 IF 陷阱门触发 不修改 IF → 允许嵌套中断 执行处理程序 → iret 恢复原 IF

3.3 异常处理流程

当 CPU 执行指令时遇到错误(如除零、缺页、无效操作码),会触发异常。异常分为三类:故障(Fault)、陷阱(Trap)、中止(Abort)。Linux 内核为每个异常注册了专用的处理函数。

3.3.1 异常分类与典型示例

类别 行为 典型向量 Linux 处理函数
故障(Fault) 返回指令重新执行 #PF (14) 缺页异常 do_page_fault()
陷阱(Trap) 返回指令下一条 #BP (3) 断点异常 do_int3()
中止(Abort) 严重错误,不恢复 #MC (18) 机器检查 do_machine_check()

3.3.2 异常处理硬件流程

  1. CPU 自动压栈:将 SS、RSP、RFLAGS、CS、RIP 压入内核栈;若涉及权限切换(CPL 变化),还会压入 SS 和 RSP。
  2. 错误码压栈:部分异常(如缺页 #PF)会压入错误码,用于指示异常原因。
  3. 查 IDT:CPU 根据向量号从 IDT 中取出门描述符,获取处理程序入口地址。
  4. 跳转执行:CPU 跳转到处理程序,执行内核注册的异常处理函数。
  5. 返回:处理完成后执行 iret 或 iretq,恢复现场并返回用户态。
关键点:异常处理程序运行在中断上下文中,不能睡眠、不能访问用户空间数据(除非明确处理)。缺页异常是唯一可以“修复”并重新执行指令的故障类型。
x86 异常处理完整流程 ① CPU 检测异常(如缺页 #PF) ② 自动压栈:SS、RSP、RFLAGS、CS、RIP ③ 压入错误码(若该异常支持) ④ 查 IDT → 获取处理程序入口 ⑤ 跳转执行内核处理函数 ⑥ iretq 恢复现场,返回用户态

3.4 中断与异常在系统调用中的作用

在 x86 传统实现中,系统调用通过 int 0x80 指令触发陷阱门,CPU 自动完成以下操作:

  • 从用户态(Ring 3)切换到内核态(Ring 0)。
  • 压栈用户态寄存器(SS、RSP、RFLAGS、CS、RIP)。
  • 根据向量号 0x80 查找 IDT,跳转到 entry_INT80_32 或 entry_SYSCALL_64(x86-64 使用 syscall 指令,但原理类似)。
现代 x86-64 优化:Linux 内核在 x86-64 上优先使用 syscall/sysret 指令,而非 int 0x80。但 IDT 仍然保留 0x80 条目用于兼容 32 位程序。下一章将详细对比两种系统调用入口。

理解 IDT、中断门与陷阱门、异常处理流程,是后续分析系统调用从 int 0x80 到 syscall 演进的基础。这些机制共同构成了 CPU 与内核之间的契约接口,确保控制权安全、高效地转移。