异常与中断
本节属于 408 选择题高频考点,重点掌握:
- 中断与异常的区别
- 中断处理过程(哪些由硬件完成,哪些由软件完成)
- 断点与现场的区别
- 单重中断、多重中断、中断屏蔽
- 异常、自陷、系统调用之间的关系
本节可以和 程序中断 I/O 方式 共同学习。
中断
中断(interrupt)是指 CPU 在正常执行程序过程中,由 外部设备 或 软件指令 触发的事件,促使 CPU 暂停当前执行,转而响应处理这些事件的一种机制。中断机制使得 CPU 能够及时响应外部输入、计时器、网络等事件,是现代操作系统和硬件协同工作的核心技术之一。
分类
中断是一个比较大的概念,我们可以按照不同的维度对其进行分类:
按照来源
根据中断来源区分:
- 外部中断:
- 触发来源:外部设备或外部事件触发,如输入设备、时钟、外部信号等。
- 响应:CPU 响应外部事件,执行相应的中断处理程序。
- 内部中断(也叫 异常):
- 触发来源:程序或 CPU 内部状态触发,如异常、错误等。
- 响应:CPU 根据内部条件触发中断,执行特定的中断处理程序。
内部 和 外部 中断触发时间有何差异?
外部中断是在 “当前指令执行完成后” 才响应;内部异常是在 “当前指令执行过程中” 检测到异常而触发,其返回位置由异常类型决定。
按照可嵌套性
执行中断的过程中是否允许 处理其他中断:
- 单级中断:CPU在执行中断服务程序时 不响应 任何新中断请求(即使优先级更高),所有中断按顺序依次串行处理。
- 多重中断(中断嵌套):CPU在执行中断服务程序时 允许响应 优先级更高的新中断请求,从而暂停当前任务去处理更紧急的事件。
按照可屏蔽性
中断是否可以被 CPU 屏蔽:
- 可屏蔽中断(Maskable Interrupt)
- 可以通过设置中断屏蔽位(如 CPU 的中断允许标志)来暂时禁止的中断。
- 通常为普通外设中断,如键盘、鼠标、网卡等。
- 不可屏蔽中断(Non-Maskable Interrupt, NMI)
- 无法被屏蔽的紧急中断,常用于处理系统级错误,如电源故障、内存校验错误等。
基础概念
- 系统中断向量 :系统中断向量是一个指向中断处理程序入口地址的指针。
- 中断向量表
(Interrupt Vector Table, IVT):
- IVT 是一个数据结构,它将中断号映射到中断处理程序的地址。
- 每个中断向量都包含中断处理程序的入口地址。
- 中断服务程序
(Interrupt Service Routine, ISR):中断处理程序是一段特殊的代码,用于响应特定的中断事件。
- 保存被中断程序的上下文(寄存器状态等)。
- 处理中断事件,例如读取硬件设备的数据、响应软件错误等。
- 恢复被中断程序的上下文,使程序能够从中断点继续执行。
CPU 可以通过 INT <中断号> 指令来执行特定的中断。当中断发生时,CPU 会根据 中断向量表基址寄存器(IVTBR)查找中断向量表的起始地址,再使用 中断号(中断类型)作为表下标找到相应的中断向量。然后,CPU 会使用中断向量中的地址,跳转到对应的中断处理程序执行。
中断处理流程
上图给出了一个详细的计算机中断处理过程的图例。不同计算机的中断处理过程不同,考察该知识点的题目也经常没有统一的标准。这里说明一个比较详细全面的过程,可以帮助大家应付绝大多数相关考题:
- 中断触发:
- 外设向计算机发送一个 IRQ(中断处理请求)。
- 中断识别:
- 处理器完成当前指令的执行,并开始中断处理过程。
- 处理器检查确定哪个中断被触发。如果中断使能,处理器会响应这个中断信号。
- 处理嵌套和屏蔽:
- 保存断点:
- 硬件 会保存当前任务的位置和状态,通常包括程序计数器(PC)、程序状态字(PSW)和其他必要的寄存器,以便中断处理完成后能够恢复。
- 中断服务寻址:
- 根据中断号在 中断向量表 中找到中断向量。
- 执行中断服务程序:
- 处理器根据中断向量跳转到相应的中断服务程序(ISR),中断服务程序包含以下内容:
- 保存现场:程序使用的通用寄存器。
- 处理中断事件(核心业务逻辑)
- 恢复现场:恢复之前保存的现场来恢复中断之前执行的任务。
- 中断返回(开中断、恢复断点):执行一条专门的中断返回指令(比如 IRET 指令),该指令将程序计数器(PC)和程序状态字(PSW)等恢复到中断前的值,然后继续执行中断之前的程序。
- 处理器根据中断向量跳转到相应的中断服务程序(ISR),中断服务程序包含以下内容:
对于中断处理过程,大家需要注意两点:
- 断点 和 现场 的区别。
- 哪些步骤是硬件完成的,哪些步骤是软件完成的。
下图给出了软硬件在中断处理过程中的职责,与上述过程相对应:
保护断点和保护现场的区别
首先是 断点 和 现场 两个词的语义差别,一般来讲,断点 就是进程在中断之前执行的位置和状态,这包含程序计数器(PC)、程序状态字(PSW / FLAGS),可能还有堆栈指针(SP)等信息。
而 现场 大多数情况下与 上下文(context)具有相同的含义,不过现场是更加口语化的词。一般来讲,现场这一概念是包含断点的(即断点是 现场或上下文 的子集)。但是在很多考察中断的题目中,倾向于把 现场 和 断点 这个词区分开来,也就是说 现场 仅代表通用寄存器的内容。
所以大家要灵活应变,还是理解中断的核心机制,才能应付出题人的模棱两可。
中断中硬件和软件的功能
- 保存断点:由 中断隐指令 自动保存(硬件 完成),保存 CPU 在中断时即将执行的指令地址,即通过寄存器
CS:IP指向的内存地址,完成中断后可通过保存的CS:IP内容恢复下一条指令的执行。 - 保存和恢复现场:在 中断服务程序 内保存(软件 完成),中断服务程序可能会使用到 CPU 中的某些寄存器,通过保存这些寄存器的内容,确保在中断程序执行完后,CPU 可以恢复到执行中断服务程序前的状态。
- 恢复断点:一般是在中断服务程序中触发(RET 指令),但是由硬件执行。
中断优先级
为了及时响应重要事件,系统会为每个中断源分配一个 中断优先级(Interrupt Priority)。当多个中断请求同时到达,或在执行一个中断服务程序期间又出现新的中断请求时,CPU 会根据中断优先级决定响应顺序。
中断优先级主要有两个作用:
- 多个中断同时到达时,决定 CPU 首先响应哪个中断。
- 发生中断嵌套时,决定新的中断是否可以打断当前正在执行的中断服务程序。
例如,若中断源 A 的优先级高于 B、C,则:
- 当 A、B 同时发出中断请求时,CPU 会优先响应 A;
- 当 CPU 正在执行 B 的中断服务程序时,若 A 发出中断请求,则 A 可以优先获得处理。
中断嵌套
中断嵌套 是指当 CPU 正在执行一个中断服务程序(ISR)时,如果出现了一个 优先级更高 的中断请求,CPU 会 暂停 当前正在执行的中断服务程序,转而响应新的中断请求。待高优先级中断处理完成后,再返回继续执行原先被暂停的中断服务程序。
这种“中断中再响应中断”的机制,就称为 中断嵌套。
单级中断
若 CPU 在执行中断服务程序期间,即使出现新的 更高优先级 中断请求,也 不予响应,则称为 单重中断。
上文介绍的 中断处理过程 就属于单重中断。其实现方式十分简单:CPU 在进入中断服务程序后关闭中断,直到中断处理结束再重新开启中断。
对于 x86 来说,可通过 FLAGS 寄存器 中的 IF 标志位控制:
- IF = 1(中断使能):允许 CPU 响应可屏蔽中断请求。
- IF = 0(中断禁止):CPU 不响应任何可屏蔽中断请求。
多重中断
多重中断(中断嵌套)是指 CPU 在执行中断服务程序期间,允许 更高优先级 的中断打断当前中断服务程序,从而提高系统对关键事件的响应速度。
要实现多重中断,CPU 必须满足两个条件:
- 重新开中断:进入中断服务程序后,需要重新开启中断(IF = 1),否则新的中断请求无法得到响应。
- 优先级控制:必须保证只有 优先级更高 的中断才能打断当前中断服务程序,而较低优先级的中断应继续等待。
第二个条件正是通过 中断屏蔽技术 实现的。
中断屏蔽
中断屏蔽(Interrupt Masking) 用于控制 当前允许响应哪些中断请求,从而实现中断优先级管理。
每个中断源对应一个 屏蔽触发器:
- 1:屏蔽该中断源;
- 0:允许该中断源。
所有屏蔽触发器组成一个 中断屏蔽字(Mask),存储在屏蔽字寄存器中,用于控制各个中断请求是否能够被 CPU 响应。
屏蔽字中的每一位对应一个中断源。当 CPU 正在执行某个中断服务程序时,可以根据当前中断的优先级修改屏蔽字,从而决定哪些新的中断请求可以继续响应。
例如,设四个中断源的优先级为:
A > B > C > D
若 CPU 正在执行 B 的中断服务程序,则通常会设置屏蔽字,使:
- A:允许(优先级更高)
- B:屏蔽(防止重复进入)
- C:屏蔽
- D:屏蔽
这样,当新的中断请求到来时:
- A 可以打断当前 B 的中断服务程序;
- C、D 只能等待 B 处理结束后再响应。
因此,中断屏蔽的作用并不是改变中断优先级,而是 保证只有优先级更高的中断才能发生中断嵌套。
注意:中断屏蔽字主要用于多重中断。
对于单重中断来说,CPU 在进入中断服务程序后直接关闭中断(IF = 0),所有可屏蔽中断都会被禁止,因此无需使用中断屏蔽字。
根据是否可以被屏蔽,中断又可分为 可屏蔽中断(Maskable Interrupt) 和 不可屏蔽中断(Non-Maskable Interrupt,NMI)。
可屏蔽中断
可屏蔽中断(IRQ) 是指可以通过设置屏蔽字暂时禁止响应的中断。这类中断通常对应普通外设事件,例如键盘输入、串口通信、USB 设备或定时器中断等。
CPU 可以根据当前正在执行的中断服务程序,动态修改屏蔽字,从而决定哪些可屏蔽中断能够参与中断嵌套。
不可屏蔽中断
不可屏蔽中断(NMI) 是指不能通过屏蔽字或 IF 标志位禁止响应的中断。这类中断通常用于处理系统中的严重错误,例如内存校验错误、电源故障等。
由于 NMI 具有最高优先级,因此即使 CPU 正在执行其他中断服务程序,也会立即响应 NMI,以保证系统的可靠性。
在硬件设计中,可屏蔽中断和不可屏蔽中断通过不同的信号线连接到 CPU。以 x86 为例:
- INTR :接收 可屏蔽中断,主要处理普通外设中断,其响应受 IF 标志位和中断屏蔽字共同控制。
- NMI :接收 不可屏蔽中断,用于处理系统严重错误,不受 IF 标志位和中断屏蔽字影响。
嵌套和屏蔽的关联
两者讨论的是 两个不同层次的问题:
- 中断嵌套(Interrupt Nesting):描述的是 CPU 是否允许 ISR 被另一个 ISR 打断。
- 中断屏蔽(Interrupt Masking):描述的是哪些中断请求有资格打断当前 ISR。
当然,当我们讨论中断嵌套中的 单级中断 和 多重中断,默认都是 可屏蔽中断。
不可屏蔽中断 是一种特殊情况,它始终可以抢占当前执行流,因此通常不纳入单重/多重中断机制来讨论。
异常
在 CPU 中,异常 是指在程序执行过程中出现的非正常或意外情况,CPU 需要暂停当前执行,转而调用操作系统提供的异常处理程序。异常机制确保系统能够安全、有效地处理错误或特殊事件,并对程序执行做出相应调整。
中断和异常的区别:
异常 发生在 CPU 内部,在一般的分类方法中可以被看为中断类型的一种(内中断),但是在有的书上将中断和异常这两个概念区分了开来:
- 异常代表 CPU 执行指令时的意外事件
- 中断代表来自 CPU 外部、与 CPU 执行指令无关的事件引起的中断
在这里注意一下即可,面对题目可以灵活分辨即可。
类型
在执行指令的过程,可能发生如下五类异常:
- 除法错误
- 触发条件:程序尝试除以零或执行非法除法操作。
- 处理方式:通常由操作系统捕获并中断程序执行,可能显示错误信息或终止程序。
- 浮点异常
- 触发条件:浮点运算出现错误,例如溢出、下溢、除以零或非法操作(如对负数开平方)。
- 处理方式:操作系统可能中断程序、设置异常标志,或产生 NaN(非数)结果。
- 缺页异常
- 触发条件:程序访问的内存页尚未加载到物理内存中。
- 处理方式:操作系统会将所需页从磁盘调入内存,更新页表后恢复程序执行。
- 保护错误
- 触发条件:程序试图进行非法操作,如写入只读内存、访问内核空间或执行特权指令。
- 处理方式:操作系统中断程序执行,防止非法访问。
- 硬件错误
- 触发条件:由硬件错误引起,如内存故障、总线错误、电源故障等。
- 处理方式:可能导致系统记录错误、尝试修复或直接宕机,具体行为依赖硬件和系统配置。
下表给出了每种类型的一个具体实例,帮助各位理解什么叫做异常:
int a = 10;
int b = 0;
int c = a / b; // 除以零,触发除法错误
float x = 0.0;
float y = sqrt(-1.0); // 无效操作,结果为 NaN(非数)
char* ptr = mmap(NULL, 4096, PROT_READ, MAP_PRIVATE, fd, 0);
char c = ptr[0]; // 当访问 ptr[0] 时,触发缺页异常
// 内核通过页调度机制将数据从磁盘加载进内存,随后程序继续执行。
int* ptr = (int*)0xFFFF0000; // 指向只读或内核地址
*ptr = 42; // 写操作触发保护错误(Segmentation Fault)
// 机器检查异常通常由硬件引发,难以模拟。
// 示例:某些平台可使用特殊指令测试内存校验错误。
// 实际触发依赖硬件环境,下面为伪代码:
char* bad_addr = (char*)0xDEADBEEF;
char c = *bad_addr; // 假设该地址引发硬件错误
自陷
自陷(Trap)是 CPU 主动触发的一类异常。当程序执行过程中出现某些需要操作系统介入处理的情况时,CPU 会暂停当前程序,保存现场,并将控制权交给操作系统的异常处理程序。
与 外部中断 不同,自陷是由 当前正在执行的指令 或 程序运行状态 引起的,而不是由外部设备发出的中断请求。
自陷的典型应用:
- 异常处理 当程序执行过程中发生错误,如除零、非法内存访问、无效指令等,CPU 会触发自陷,进入相应的异常处理程序。
- 系统调用
用户程序需要请求操作系统提供服务(如打开文件、创建进程、分配内存等)时,会执行专门的 陷阱指令(如 x86 的
syscall),主动触发自陷,进入内核态执行系统调用处理程序。 - 调试断点
调试程序时,在断点位置插入断点指令(如 x86 的
int3)。程序执行到该指令时,会触发自陷,暂停运行,等待调试器接管。
自陷处理过程(简化版)如下:
- CPU 检测到自陷条件(异常、系统调用或断点);
- 保存当前程序的执行现场(如 PC、寄存器等);
- CPU 切换到内核态,转入操作系统预设的自陷处理程序;
- 操作系统根据自陷原因完成异常处理或系统服务;
- 若程序可以继续执行,则恢复现场并返回;否则终止程序。
陷阱指令 是一种由程序 显式执行 的特殊指令,其作用是主动触发自陷。系统调用通常就是通过陷阱指令实现的,因此它是用户程序进入内核态、请求操作系统服务的重要机制。
通过自陷机制,CPU 能够在程序运行过程中及时将控制权交给操作系统,既能处理各种异常,又能安全、高效地完成系统调用等内核服务。