大家好,我叫徐锦桐,个人博客地址为www.xujintong.com,github地址为https://github.com/jintongxu。平时记录一下学习计算机过程中获取的知识,还有日常折腾的经验,欢迎大家访问。
一、异常控制流(ECF)
现代系统通过使控制流发生突变来对这些情况做出反应。一般而言,我们把这些突变称为异常控制流(Exceptional Control Flow,ECF)。
ECF是操作系统用来实现I/O、进程和虚拟内存的基本机制。应用程序通过使用陷阱(trap)或者系统调用(system call)的ECF形式,向操作系统请求服务(比如向磁盘写入数据、创建一个新进程、终止当前进程,都是通过系统调用来实现的)。
二、异常
异常是异常控制流的一种形式,它一部分由硬件实现,一部分由操作系统实现。
异常(exception)就是控制流中的突变,用来响应处理器状态中的某些变化。
状态变化称为事件(event)。事件的发生可能和当前指令的执行直接相关。比如,发生虚拟内存缺页、算数溢出,或者一条指令试图除以0。事件也可能和当前指令的执行没有关系。比如,一个系统定时器产生信号或者一个I/O请求完成。
系统中可能的每种异常都分配一个唯一的非负整数的异常号,并且每个异常号都对应了对应异常的处理程序。异常处理程序都存储在异常表中,通过唯一的异常号可以在异常表中查找对应的异常处理程序。
异常表的起始位置放在一个叫做异常表基址寄存器的特殊CPU寄存器中,异常号是到异常表中的索引。
在IA-32(也就是32位Linux系统中) ,这个异常表是 IDT (Interrupt Descriptor Table ),异常表的起始位置放在IDTR (IDT register)。在IDT中有256种不同的异常类型,具体如下:
三、异常的类别
异常可以分为四类:中断(interrupt) 、陷阱(trap) 、故障(fault) 、终止(abort) 。
3.1 中断
中断的概念
CPU在执行指令时,收到某个中断信号转而去执行预先设定好的代码,然后再返回到原指令流中继续执行,这就是中断机制。
其实举个例子来说:就是你写着作业呢,你舍友在蹲坑给你打个电话让你拿个卫生纸(发生事件),你保存下当前写的作业的状态(保存现场),然后给舍友去送纸(处理中断),送完回来继续接着写作业(恢复现场)。
中断包括可屏蔽中断 和不可屏蔽中断 。可屏蔽中断(EFLAG 寄存器中的IF 位为0 )就是该中断可以被CPU屏蔽不管,也可以等待当前指令执行完然后再处理该中断。可屏蔽中断一般用于优先级较低的中断,一般来自IO设备。不可屏蔽中断(EFLAG 寄存器中的IF 位为1)是需要CPU立即处理该事务,不可屏蔽中断发生时,将当前的指令和状态(各个寄存器的值)存储在栈中,方便CPU处理完中断后继续恢复中断前的工作。
中断信号由外部硬件产生,并且中断是异步的,也就是说CPU不知道什么时候会产生中断,所以CPU每次取指前都要查看一下中断引脚查看是否有中断产生。
在IA-32架构中,中断产生后会通过中断号选择对应的中断处理程序,执行预先设计好处理该中断的代码,处理完成后再返回。
中断基本流程
异常的流程和中断的流程大差不差,只有一些细节不一样。
- 响应中断
系统要求中断请求信号一直保持到CPU对其进行中断响应为止。CPU每次取指前都会查看一下中断引脚如果有有效值,并且IF=1(不可屏蔽中断),CPU就会向发送中断信号的外设发送一个低电平有效的电信号,表示已经收到了你的中断信号。
- 关中断
如果中断处理程序在保存现场的过程中产生了新的中断,一些还没来得及被保存的信息可能会被破坏。因此,必须在此过程中屏蔽外部中断。CPU自动将状态标志寄存器FR或EFR的内容压入堆栈保护起来,然后将FR或EFR中的中断标志位IF与陷阱标志位TF清零,从而自动关闭外部硬件中断。
- 保护断点
保护断点就是将CS和IP/EIP的当前值压入堆栈保存,为了中断处理完毕后能够继续执行中断前的下一个指令。
- 识别并跳转到对应的中断处理程序
在IA-32中,CPU会通过中断号在IDT(中断向量表)中找到对应的中断处理程序。
- 中断处理上下部
进入中断后,当前系统处于关中断,也就是其他所有的代码都在等待当前中断处理完成,所以需要当前中断处理的要快。但是有的时候中断处理的工作量并不小,那要怎么办呢。就是将中断分为上下部,上部紧急需要立即处理完的,但是该中断也有不紧急的部分就分为下部,下部进入调度,可以以后再做,因为下部不是很着急做。
- 恢复现场
恢复中断前各个寄存器的值。
- 中断返回
栈中弹出CS和IP/EIP中断前的值,执行中断前的下一条指令。
3.2 陷阱
陷阱是有意的异常,是执行一条指令的结果。陷阱最重要的用途 是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用。
用户程序经常需要向内核请求服务,比如读一个文件(read)、创建一个新的进程(fork)、加载一个新的程序(execve),或者终止当前进程(exit)。为了允许对这些内核服务的受控的访问,处理器提供了一条特殊的"syscall n"指令,当用户程序想要请求服务n时,可以执行这条指令。执行 syscall 指令会导致一个到异常处理程序的陷阱,这个处理程序解析参数,并调用适当的内核程序。
3.3 故障
故障由错误情况引起,它可能能够被故障处理程序修正。当故障发生时,处理器将控制转移给故障处理程序。如果处理程序能够修正这个错误情况,它就将控制返回到引起故障的指令,从而重新执行它。否则,处理程序返回到内核中的 abort 例程,abort 例程会终止故障引起的应用程序。
故障的经典例子是缺页异常。
3.4 终止
终止是不可恢复的致命错误造成的结果,通常是一些硬件错误,比如DRAM或者SRAM位被损坏时发生的奇偶错误。终止程序从不将控制返回给应用程序。处理程序将控制返回给一个 abort 例程,该例程会终止这个应用程序。