中断在程序执行期间随机发生 , 作为对硬件信号的回应 . 系统硬件使用中断处理处理器外的事件 , 软件可以通过 INT n 指令产生中断 .

执行指令时处理器检测到错误会产生异常 , 比如除 0 .

1. 中断源

中断源分成两类 :

  • 外部 ( 硬件产生的 ) 中断
  • 软件产生的中断

外部中断通过处理器的引脚或者 local APIC 接收 , 处理器的基本中断引脚是 LINT[1:0] , 连接到 local APIC . 开启 local APIC 时 , 可以通过 APIC 的本地向量表 ( LVT ) 编程 LINT[1:0] , 关联到处理器的任何异常或中断向量 .

global / hardware 关闭 local APIC , 这些引脚相应配置为 INTR 和 NMI 引脚 . 使能 INTR 引脚向处理器发出发生外部中断的信号 . 处理器从系统总线读取外部中断控制器 ( 比如 8259A ) 提供的中断向量号 . 使能 NMI 引脚发出 NMI 的信号 .

通常处理器的本地 APIC 连接到一个 system-based IO APIC , IO APIC 引脚接收到外部中断可以通过系统总线或者 APIC 串行总线传递给本地 APIC . IO APIC 确定中断的向量号并发送给本地 APIC . 多处理器系统 , 处理器可以通过系统总线或者 APIC 串行总线发送中断给另一个处理器 .

通过 INTR 引脚或者本地 APIC 发送给处理器的任何外部中断都被称作一个可屏蔽硬件中断 . 可以通过 INTR 引脚传递的可屏蔽硬件中断包括所有的 IA-32 架构定义的 0-255 中断向量 ; 可以通过本地 APIC 传递的包括中断向量 16-255 .

软件通过 INT n 指令产生的中断无法通过 EFLAGS 寄存器的 IF 标志屏蔽 .

2. 异常

2.1. 异常源

异常源有三种 :

  • 处理器探测到的程序错误异常
  • 软件产生的异常
  • 机器检查异常

处理器在执行程序期间检测到程序错误时产生一个或多个异常 , Intel 64 和 IA-32 架构给每个处理器可探测的异常定义了向量号 .

软件通过 INTO , INT1 , INT3BOUND 指令产生异常 .

P6 和 Pentium 处理器提供了内部和外部机器检查机制 , 检查内部芯片硬件和总线 transaction 的操作 .

2.2. 异常分类

根据异常被报告的方式和造成异常的指令能否在不丢失程序连续性的前提下重新执行 , 分成 fault , trap 和 abort .

  • fault
    fault 可以被修正 , 并且修正后程序可以重新执行 , 不会丢失连续性 . 报告 fault 时 , 处理器恢复机器状态到执行 fault 指令前的状态 , fault 处理函数的返回地址指向发生 fault 的指令 , 而不是 fault 后的指令 .

  • trap
    发生 trap 的指令执行后立即报告 trap , 可以在不丢失程序连续性的前提下继续执行程序 , trap 处理函数的返回地址指向发生 trap 后要执行的指令 .

  • abort
    abort 通常不会报告造成异常的准确位置 , 无法重新执行发生 abort 的指令 . abort 用于报告严重的错误 , 比如硬件错误和系统表中的不一致值和非法值 .

3. 程序重启

为了保证程序在处理异常或中断后可以重新执行 , 所有的异常 ( 除了 abort ) 都可以在一条指令的边界报告异常 , 所有的中断可以在一条指令的边界进行处理 .

如果在跳转指令发生了陷入 , 返回指令指针会反映出跳转 —- 例如 , 如果执行 JMP 指令时发生陷入 , 返回指令指针指向 JMP 指令的目标地址 , 而不是 JMP 指令后的下一个地址 .

中断严格支持被中断程序在保证连续性的前提下重启程序 . 如果执行的指令有一个重复前缀 , 中断在当前循环结束时处理 , 将寄存器设置为执行下一次循环 .

指令的投机执行不会影响处理器处理中断 .

4. 不可屏蔽中断

NMI 有两种产生方式 :

  • 外部硬件使能 NMI 引脚
  • 处理器在系统总线或者 APIC 串行总线收到一个包含 delivery mode NMI 的消息

处理器收到 NMI 时 , 立即调用中断向量 2 执行的 NMI 处理函数 , 还会保证其他中断 ( 包括 NMI ) 不会在中断处理函数执行期间产生 .

执行 NMI 中断处理函数时 , 处理器阻止后续 NMI 的传递 , 直到执行 IRET 指令 , 防止 NMI 处理函数的嵌套执行 .

IRET 指令的执行开启 NMI , 即使指令本身产生了 fault .

5. 开启和关闭中断

处理器根据处理器的状态和 ELFAGS 寄存器的 IF 和 RF 标志阻止某些中断的产生 .

IF 标志可以关闭处理器通过 INTR 引脚或本地 APIC 收到的可屏蔽硬件中断 , 但是不会影响发送到 NMI 引脚或者通过本地 APIC 发送的 NMI 消息传输的 NMI 中断 , 也不会影响处理器产生的异常 .

IF 标志可以通过 STICLI 设置和清除 , 只有 CPL 小于等于 IOPL 时可以执行 .

RF 标志阻止调试异常的产生 .


切换栈时 , 软件通常使用一对指令 , 比如 :

MOV SS,AX
MOV ESP,StackTop

如果在 SS 段描述符加载后 , ESP 寄存器加载前发生了中断或异常 , 栈空间内线性地址的两部分在中断或异常处理函数期间就不一致 .

为了解决这个问题 , 处理器在执行 MOV to SSPOP to SS 指令后 , 阻止一些事件的发生 .

Intel 推荐软件使用 LSS 指令一起加载 SS 和 ESP , 上述问题不适用于 LSS , LSS 指令不阻止上面列出的事件 .

6. IDT

IDT 将每个异常或中断向量和处理相关的异常或中断的处理函数的门描述符关联起来 , 总共只有 256 个中断向量 , 因此 IDT 最多只需要包含 256 了描述符 .

IDT 可以保存到线性地址空间的任何位置 , 处理器通过 IDTR 定位 IDT .

IDT 包含三种门描述符之一 :

  • 任务门描述符
  • 中断门描述符
  • 陷入门描述符

任务门描述符和 GDT 或者 LDT 中的任务门相同 , 包含中断处理函数的 TSS 的段选择器 , 因此其调用方式和 CALL 指令调用程序的方式一致 .

中断门和陷入门类似调用门 , 包含跳转到一个处理函数使用的远指针 , 调用方式和 CALL 调用调用门的方式类似 .

如果中断处理函数运行在更高的特权级 , 处理器执行时需要切换栈 ; 如果运行在相同的特权级 , 处理器保存 EFLAGS , CS , EIP 的当前状态到当前栈 .

必须使用 IRET 指令从处理函数返回 , 除了恢复保存的标志到 EFLAGS 寄存器外 , IRET 指令和 RET 类似 . 如果发生了栈切换 , IRET 指令还会在返回时切换回被中断程序的栈 .

通过 IDT 中的任务门访问处理函数时 , 需要切换任务 . 在单独的任务中处理异常或者中断有以下优势 :

  • 被中断程序的整个上下文都自动保存 .
  • 新的 TSS 允许处理函数在处理中断或异常时使用新的特权级 0 , 如果当前的特权级为 0 的栈被损坏时发生了异常或中断 , 通过提供处理函数一个新的特权级为 0 的栈可以防止任务门访问处理函数时系统崩溃 .
  • 通过分配一个单独的地址空间 , 可以将处理函数进一步和其他的任务隔离 , 通过分配一个单独的 LDT 实现 .

通过单独的任务处理中断的劣势是任务切换时必须保存的机器状态很多 , 导致其比使用一个中断门更慢 , 造成更多的中断延迟 .

7. 错误代码

一个异常条件关联到一个特定的段选择器或 IDT 向量时 , 处理器在处理函数的栈上压入一个错误代码 , 后者包含段选择器的索引 , 以及下面的三个标志 :

  • EXT
    外部事件 , 设置时表明异常在传递程序外部的事件时 , 比如中断或者更早的异常 . 如果异常在传递软件中断时发生 , 清除这个标志 .

  • IDT
    描述符的位置 , 设置时表明错误代码的索引域指向一个 IDT 中的门描述符 ; 清除表明指向 GTD 或者 LDT 中的一个描述符 .

  • TI
    GDT / LDT , 只有 IDT 标志清除时使用 , 设置是 LDT , 清除是 GDT .

如果错误代码除了 EXT 外都被清除 , 可能是空的错误代码 , 表明错误不是由引用特定的段引起 , 或者在操作中引用了空的段选择器 .

和 32 位模式相比 , 64 位模式 :

  • 自动压栈 SS:RSP , 而不是只有 CPL 改变时
  • IRET 自动出栈 SS:RSP , 而不是只有 CPL 改变时
  • 实现了传统的栈切换机制的修改版和栈切换机制的替代品 , 成为中断栈表 ( IST )
  • 栈切换和传统模式类似 , 只是不从 TSS 加载新的 SS 选择器 , 新的 SS 强制为 NULL