异常控制流,就是你本来按计划的进行做事,突然来了临时事件,你不得不进行处理。现代操作系统动过使控制流发生突变来对这些情况作出反应。通常我们把这些突变成为异常控制流。
异常发生层次
- 硬件层: 硬件检测到的事件会触发控制突然转移到程序异常处理程序
- 操作系统层: 内核通过上下文切换将控制从一个用户进程转移到另一个用户进程
- 应用层: 一个进程可以发送信号到另一个进程,而接收者会将控制突然转移到它的一个信号处理程序,一个程序可以通过回避常规的栈规则,并执行到其它函数中任意位置的非本地跳转来对错误作出反应。
补充(异常发生在各层次中)异常
异常时异常控制流的一种形式,它一部分由硬件实现,一部分由操作系统实现。当处理器状态发生一个重要的变化时,处理器正在执行当前某个指令I。在处理器中,状态被编码为不同的位跟信号。状态变化成为事件,事件可能和当前指令的执行直接相关。比如发生虚拟内存缺失页,算术溢出,或者一条指令试图除以零。另一方面,事件也可能和当前执行的指令没关系,比如一个系统定时器的产生或者一个I/O请求完成。
异常处理程序完成处理后,会发生以下三种情况中的一种: - 处理程序将控制返回给当前指令I.current,即当前事件发生时正在执行的指令。
- 处理程序将控制返回给I.next,如果没有发生异常,将会执行下一条指令。
- 处理程序终止被中断的子程序。
异常处理
异常处理需要硬件与软件的紧密合作。系统中的每种类型的异常都分配了唯一的非负整数的异常号。其中一些编号由处理器设计者分配的,其它编号是由操作系统内核的设计者分配。当系统启动时,操作系统分配和初始化一章称为异常表的跳转表。当处理器检测到发生一个事件,并且确定了相应的异常编号后。随后,处理器触发异常,方法是执行间接调用,通过异常表的表目,转到相应的处理程序。异常号是转到异常的索引,异常表的启始地址存放在一个叫做异常表基址寄存器(exception table base register)的特殊CPU寄存器里。
异常类似于过程调用,但是有一些重要的不同之处:
- 过程调用时,在跳转到处理程序之前,处理器将返回地址压入栈中。然而,根据异常的类型,返回地址要么是当前指令(当事件发生时正在执行的指令),要么是下一条指令(如果事件不发生,将会在当前指令后执行的指令)。
- 处理器也会把一些额外的处理器状态压入栈中,在处理程序返回时,重新开始被中断的程序会需要用到这些状态。比如X86-64系统会将包含当前条件码的EFLAGS寄存器和其它内容压入栈中。
- 如果控制从用户转移到内核,所有这些项目都被压到内核栈中,而不是压到用户栈中。
- 异常处理运行在内核模式下,这意味着它们对所有的系统资源都有完全访问权限。
异常类别
异常可以分为四类:中断(interrupts) 、陷阱(traps) 、故障(faults)、 终止(aborts)。
类别 | 原因 | 异步/同步 | 返回行为 |
---|---|---|---|
中断 | 来自I/O设备的信号 | 异步 | 总是返回到下一条指令 |
陷阱 | 有意的异常 | 同步 | 总是返回到下一条指令 |
故障 | 潜在可恢复的错误 | 同步 | 可能返回到当前指令 |
终止 | 不可恢复的错误 | 同步 | 不会返回 |
- 中断:
- 陷阱:
- 故障:
- 终止: