中断

中断（Interrupt），又称插断，在计算机科学中是指处理器接收到来自硬件或软件的信号，提示发生了某个事件，应予以注意，这种情况就称为中断。

通常，在接收到来自外围硬件（相对于中央处理器和内存）的信号，或来自软件的信号之后，处理器将会进行相应的硬件／软件处理。发出这样的信号称为进行中断请求（interrupt request，IRQ）。硬件中断导致处理器通过一个执行资讯切换（context switch）来保存执行状态（以程序计数器和程序状态字等寄存器信息为主）；软件中断则通常作为CPU指令集中的一个指令，以可编程的方式直接指示这种执行资讯切换，并将处理导向一段中断处理代码。中断在计算机多任务处理，尤其是即时系统中尤为有用。这样的系统，包括运行于其上的操作系统，也称“中断驱动”（interrupt-driven）。

## 总览

中断是用以提高计算机工作效率、增强计算机功能的一项重要技术。最初引入硬件中断，只是出于性能上的考量。如果计算机系统没有中断，则处理器与外部设备通信时，它必须在向该设备发出指令后进行忙等待（Busy waiting），反复轮询该设备是否完成了动作并返回结果。这就造成了大量处理器周期被浪费。引入中断以后，当处理器发出设备请求后就可以立即返回以处理其他任务，而当设备完成动作后，发送中断信号给处理器，后者就可以再回过头获取处理结果。这样，在设备进行处理的周期内，处理器可以执行其他一些有意义的工作，而只付出一些很小的切换所引发的时间代价。后用于CPU外部与内部紧急事件的处理、机器故障的处理、时间控制等多个方面，并产生通过软件方式进入中断处理（软中断）的概念。

在硬件实现上，中断可以是一个包含控制线路的独立系统，也可整合进存储器子系统中。对于前者，在IBM个人机上，广泛使用可编程中断控制器（Programmable Interrupt Controller，PIC）来负责中断响应和处理。PIC连接在若干中断请求设备和处理器的中断引脚之间，从而实现对处理器中断请求线路（多为一针或两针）的复用。作为另一种中断实现的形式，即存储器子系统实现方式，可以将中断端口映射到存储器的地址空间，这样对特定存储器地址的访问实际上是中断请求。

中断可分为如下几种：

硬件中断（Hardware Interrupt）：

- 可屏蔽中断（maskable interrupt）。硬件中断的一类，可通过在中断屏蔽寄存器中设定位掩码来关闭。
- 非可屏蔽中断（non-maskable interrupt，NMI）。硬件中断的一类，无法通过在中断屏蔽寄存器中设定位掩码来关闭。典型例子是时钟中断（一个硬件时钟以恒定频率—如50Hz—发出的中断）。
- 处理器间中断（interprocessor interrupt）。一种特殊的硬件中断。由处理器发出，被其它处理器接收。仅见于多处理器系统，以便于处理器间通信或同步。
- 伪中断（spurious interrupt）。一类不希望被产生的硬件中断。发生的原因有很多种，如中断线路上电气信号异常，或是中断请求设备本身有问题。

软件中断（Software Interrupt）：

- 软件中断。是一条CPU指令，用以自陷一个中断。由于软中断指令通常要运行一个切换CPU至内核态（Kernel Mode/Ring 0）的子例程，常用于实现系统调用（System call）。

处理器通常含有一个内部中断屏蔽位，并允许通过软件来设定。一旦完成设定，所有外部中断都将被系统忽略。这个屏蔽位的存取速度显然快于中断控制器上的中断屏蔽寄存器，因此可提供更快速地中断屏蔽控制。

如果一个中断使得机器处于一种确定状态，则称为精确中断（precise interrupt）。精确中断须保证：

- 程序计数器的值保存在已知位置。
- 程序计数器所指向的指令之前的所有指令已执行完毕。
- 程序计数器所指向的指令之后的所有指令不可执行。如果中断信号到来后而转入处理前发生了任何针对寄存器／内存的更改，都必须予以还原。
- 程序计数器所指向的指令地执行状态已知。

倘无法满足以上条件，此中断称为非精确中断（imprecise interrupt）。

中断尽管可以提高计算机处理性能，但过于密集的中断请求／响应反而会影响系统性能。这类情形称为中断风暴（interrupt storm）。

## 中断的种类

### 状态触发

在依状态触发的中断系统中，一个等待响应的中断会在中断请求线路上以特定的电位标示，如高电位（1）或低电位（0）。当一个设备希望发送中断信号时，它驱动中断请求线路至相应的电位，并在CPU发出强制停止命令或处理所请求的中断事件之前始终保持。

一般而言，处理器在总线周期的特定时点响应中断的输出/ 输入。如果在某次采样时刻中断尚未触发，则在下一次采样前，处理器都不会认为有中断发生。可以应用这个特性，避免响应在噪音较高的线路上出现的伪中断。

中断设备可设计成与其他设备共享一条状态触发中断线路。中断线路应该包含一个特定的升／降压电阻，用于在无中断请求时为线路电位复位。中断设备在请求中断时会保持中断线路为有效电位，而没有请求中断时则令该线路置空。只要有一个或以上的设备发出中断信号，线路都会处于有效的电位。

由于可共享线路的便利，一些应用倾向于使用该类中断。当CPU检测到中断线路被断言后，就会逐一检查各共享设备，直至发现请求设备并处理之。当处理完毕后，继续检查中断线路，倘中断线路仍为有效电平则重复之前的步骤。在检查中断设备的顺序上也可做一定规划，比如优先检查那些频繁请求中断的设备，以加快中断处理，改善系统性能。

此类中断模式也有严重问题。只要还有任何设备的中断请求尚未处理，线路就会一直保持有效电平状态，而这将导致CPU没有机会去探查其他设备所发生的状态变化。推迟服务低优先级设备也不可行，因为这会防止对高优先级设备的探查。倘若在线路上有一个设备持续发送请求而CPU不知道怎样对其进行服务，则这个设备就会持久并排他地占有中断线路。

早期的PCI（外设互连标准）标准出于上述效率层面的理由规定其周边须使用状态触发中断。

### 边沿触发

在依边沿触发的中断系统中，中断设备通过向中断线路发送一个脉冲来表示其中断请求。脉冲可以为上升沿或下降沿。在发送完脉冲后设备立即释放中断线路。如果这个脉冲太短，以至于I/O轮询不足以确保知悉其存在，则有必要使用专门的硬件设备来辅助对边沿触发的探查。

中断设备可设计成与其他设备共享一条边沿触发中断线路。中断线路应该包含一个特定的上拉／下拉电阻，用于在无中断请求时为线路电平复位。设备通过发送一个脉冲作为其中断信号。如果多个设备在近乎相同的时间内发送脉冲，则会在线路上合并成一个信号。为防止中断丢失，CPU必须在一个脉冲之后的下一个边沿（如果脉冲为上升沿则其下一个边沿就是下降沿）立即触发。收到中断请求后CPU立即查询各中断设备以定位中断源。

边沿触发中断不会遭受状态触发中断在共享中断引脚时所遇到的问题。低优先级设备的服务可任意推迟，而CPU仍会收到高优先级设备的中断请求。一个即便是频繁发生的伪中断也不会影响正常设备的中断请求。但是，边沿触发中断容易丢失，特别是当中断被有意屏蔽时。在不引入锁存器的情况下，在屏蔽时段发送的中断信号不可恢复。在早期的计算机系统中因为中断丢失而导致处理不能继续的情况时有发生。现代中断硬件多包含有一个或一组中断状态锁存器，用以暂存一逝而过的中断请求。在对边沿触发中断硬件进行编程时，应检查这些中断状态寄存器以确保请求事件不会丢失。

已经过时的ISA（工业标准架构）标准使用边沿触发中断，但不规定其实现必须能够共享线路。

### 混合模式

一些系统使用状态触发与边沿触发兼顾的混合中断模式。其硬件不但探测脉冲，也验证中断信号是否保持一段时间。

非可屏蔽中断多使用混合模式。由于非可屏蔽中断多与重要的系统异常事件相关，十分有必要确保对其中断信号的捕捉快速而正确。这种两步骤探查方式能够有效减轻错误中断或遗失中断给系统带来的影响。

### 消息信号（Message-signalled）

消息信号式中断并不直接通过对特定物理线路进行断言／发送脉冲来通知一个中断。这类中断设备通过在某种通讯媒介（一般是计算机总线）上发送一个有逻辑含义的消息（一串／排比特码）来实现中断请求。中断消息可以是通讯总线协议中专门为中断预留的类型，也可以是一个现有的类型，如内存写操作。

消息信号式中断在行为上与边沿触发中断类似，因为它们都是发送一个瞬间的信号。中断处理软件的对此类中断的处理方式也类似于边沿触发中断：如果两个消息相同，则可以合并。消息信号中断向量（中断处理程序的地址）也可以共享，就如同物理线路可以被共享一般。

由于中断消息的识别基于特定的比特码序列而不是物理线路上的单个信号，可以有效地通过设定不同的中断比特码来划分和处理不同类型的中断。另外，使用串行或并行总线都可以传递中断消息。

由于无论状态触发还是边沿触发都在使用共享线路时存在线路竞争问题，而物理线路数本身也是稀缺资源，不可能被各中断源分别独占，所以消息信号中断是一个解决此问题的较好替代方案。消息信号中断的本质差别在于其中断请求运行在单纯的物理线路之上，具有特定的逻辑含义。这种区别好比计算机网络体系中第一层（物理层）和第二层（链路层）的差别。使用具有逻辑含义的中断请求，可以把诸请求区分开来，形成多条虚通路，而运行于一条物理总线之上。

PCI Express串行总线标准即使用此种中断。

## 应用

### 常见应用

中断的典型应用包括系统时钟、磁盘输入输出操作、断电信号以及软件自陷等。

- 系统时钟通过一个计数器（多基于某种振动频率）定期向CPU发出中断，CPU通过专门的时钟中断处理程序来保持计时。现代操作系统对系统时钟的另一个主要应用是为进程切换提供时机。一旦时钟中断发生，程序计数器会被自动压栈，而此时操作系统就有机会将程序状态及内存映像转存至别处，并调用进程调度程序来选择下一个进程，并将其进程状态，包括程序计数器，导入寄存器。这样下一个程序就可以运行。应注意进程调度程序的调度时机不止于时钟中断。
- 磁盘中断标识某个磁盘设备完成了数据的发送／接收。磁盘中断发生后，等待这个中断的进程可以（但未必，这取决于进程调度程序当时的判断）继续执行。
- 断电中断指示计算机能源即将丧失，计算机可以相应中断程序作有序的关机处理。

### 实例：8086A的中断系统

8086A处理器是INTEL公司于20世纪70年代末推出的一款16位处理器。该处理器在中断处理上有如下特色，这些特色也为后续INTEL处理器所共有。

#### 引脚

8086A提供两个中断引脚：第17引脚NMI，和第18引脚INTR。前者用于接收非可屏蔽中断，后者则接收可屏蔽中断。通常，INTR引脚与中断控制器（如8259A）相连，后者再分别与各设备的中断请求引脚连接。除了INTR外，8086A还将自身的16位地址总线（配合M/IO引脚并通过译码器）及8位数据总线与8259A连接，并将INTA引脚与8259A的同名引脚相连。

#### 中断向量表

在存储器地址空间中，规定最低的1K空间，即00000H到003FFH为中断向量表。全表共含256个中断向量，每个向量的长度为4字节，包含中断处理程序的起始地址。共有从0到255共256个中断类型码，每个中断类型码对应的中断向量所在地址为该类型码乘以4。举例而言，如果中断类型码为1，则对应中断向量所在地址为00004H；如果中断类型码为33，则对应中断向量所在地址为00084H。这样，如果已知一个中断类型码，则需要通过两次地址转换（中断类型码到中断向量表地址；中断向量表地址到中断处理程序地址）才能到达中断处理程序。另外应注意每一个中断向量所包含的地址是以低二字节存储偏移量，高二字节存储段地址的形式存储目标地址值的。

在全部256个中断中，前32个（0—31）为硬件系统所预留。后224个可由用户设定。在初始化8259A时，可设定其上各中断引脚（共8条）对应的中断类型码。同时，将对应此中断之处理程序的起始地址保存在该中断类型码乘4的地址位中，作为中断向量。

在INTEL后续的32位CPU中，使用中断描述符表来代替中断向量表。中断描述符表的起始地址由中断描述符表寄存器（IDTR）来定位，因此不再限于底部1K位置。另一方面，中断描述符表的每一个项目——称作门描述符——除了含有中断处理程序地址信息外，还包括许多属性／类型位。门描述符分为三类：任务门、中断门和自陷门。CPU对不同的门有不同的调用（处理）方式。

##### 中断处理过程

在实际运行中，一旦设备通过某引脚N向8259A发出中断指令，后者便向8086A的INTR引脚发送中断信号。8086A通过INTA引脚通知8259A中断有效（这个过程实际上还包括对此8259A的选址），后者即通过地址总线将对应引脚N的中断类型码（已预先存好，见上节）发送给CPU。CPU得到中断类型码后，先进行现场保护，主要包括：

- 状态寄存器FLAGS压栈（同时堆栈寄存器SP-2）。
- 关闭中断（将FLAGS寄存器的IF位置零）。
- 将当前代码段寄存器CS和程序计数器IP压栈（同时堆栈寄存器SP-4）。

现场保护完成后，CPU开始按照前述的两步骤翻译中断程序入口地址。在得到中断处理程序地址之后但调用中断处理程序之前，CPU会再检查一下NMI引脚是否有信号，以防在刚才的处理过程中忽略了可能的NMI中断。NMI的优先级始终高于INTR。

中断处理程序虽然是由程序员编写，但须循一定规范。作为例程，中断处理程序应该先将各寄存器信息（除了IP和CS，此二寄存器现已指向当前中断程序）压入堆栈予以保存，这样才能在中断处理程序内部使用这些寄存器。在程序结束时，应该按与压栈保护时相反的顺序弹出各寄存器的值。中断程序的最后一句始终是IRET指令，这条指令将栈顶6个字节分别弹出并存入IP、CS和FLAGS寄存器，完成了现场的还原。

当然，如果是操作系统的中断处理程序，则未必——通常不会——还原中断前的状态。这样的中断处理程序通常会在调用完寄存器保存例程后，调用进程调度程序（多由高级语言编写），并决定下一个运行的进程。随后将此进程的寄存器信息（上次中断时保存下来的）存入寄存器并返回。在中断程序结束之后，主程序也发生了改变。

#### 相关指令

一些与中断控制相关的指令包括：

- `CLI` 关闭中断（实为将FLAGS的IF位置0）。
- `STI` 开启中断（实为将FLAGS的IF位置1）。
- `INT n` 调用中断子程序，n为中断类型码。DOS系统有一个系统调用API即为INT 21H。
- `INT0` 先判别FLAGS的OF位是否为1，如是则直接调用类型为4的中断子程序，用以处理溢出中断。
- `IRET` 将栈顶6个字节分别弹出并存入IP、CS和FLAGS寄存器，用以返回中断现场。

#### 其它

- 与中断处理相对的是轮询(Polling)