异常
什么是异常
异常是异常控制流的一种形式,它一部分由硬件实现,一部分由操作系统实现。因为它们有一部分是由硬件实现的,所以具体细节将随系统的不同而有所不同。然而,对于每个系统而言,基本的思想都是相同的。
大致如下:
异常控制流
下述程序中的write和_exit函数就进行了系统函数的调用:
// hello world
int main()
{
write(1, "hello, world\n", 13);
_exit(0);
}
其对应的汇编为:
.section .data
string:
.ascii "hello, world\n"
string_end:
.equ len, string_end - string
.section .text
.globl main
main:
First, call write(1, "hello, world\n", 13)
movq $1, %rax write is system call 1
movq $1, %rdi Arg1:stdout has descriptor 1
movq $string, %rsi Arg2:Hello world string
movq $len, %rdx Arg3:string length
syscall Make the system call
Next, call exit(0)
movq $60, %rax _exit is system call 60
movq $0, %rdi Arg1:exit status is 0
syscall Make the system call
操作系统的系统调用,在汇编中是使用寄存器进行参数传递
与过程之间的参数调用类似,操作系统的系统调用,同样使用了6个寄存器来传递参数,但寄存器的使用与caller的有所不同。操作系统的系统调用,使用了%rdi,%rsi,%rdx,%r10,%r8,%r9这六个寄存器,并以%rax作为返回值
处理器的状态:在CPU中有状态寄存器,会标识处理器的状态。处理器的状态一旦发生重要变化,则称之为事件。事件会发出请求信号,以告诉CPU,称之为中断请求。处理中断请求——由事件引起了异常,使控制流发生了变化。原本控制流应该是在执行用户写的程序,而发生异常后,去执行了另外的程序——异常处理程序。
事件并不一定与当前执行的指令有关。例如,访问时发现数据不在内存中时,就引起了异常,由异常处理程序将需要的数据从辅存调往主存,这是与执行有关的事件。而ctrl+C强制中断,则与当前执行的指令无关。
异常处理程序属于操作系统的一部分。一旦执行完处理程序后,还要回到原来的代码中继续执行。返回的位置根据执行的结果而定,
1、可能会回到引起异常的指令上,重新执行,
2、也可能会返回到引起异常的指令的下一条指令。当前指令执行完后,停下来去执行异常处理程序,写入到CPU中。
3、还可能是程序完全被中断(例如蓝屏、死机),此时机器出了问题,异常处理程序已经解决不了了
异常表
异常的编号从0——255,每个编号对应一个异常处理程序。在操作系统中,专门有一张称为异常表的表(有时也称作中断向量表),一共有256项。每个表项中存放的是对应的异常处理程序的入口地址。这张表是常驻内存的,由专有寄存器去找到那个表
在64位机下,每次都是将异常编号乘以8来找到对应的地址
内核栈
异常处理程序运行在内核模式下,可以访问所有资源。内核栈中保存了能够帮助程序恢复运行的信息,例如:返回地址、当前条件码
包括鼠标、键盘的交互,都是通过异常执行的
Linux:异常
Windows:中断
常见异常
同步异常:与程序的执行息息相关
被执行的指令所引起的事件(自己愿意引起的异常)
包括:
陷阱:跟踪时设置的断点,称之为陷阱(程序执行到断点时的停止)。会返回到下一条指令
故障:例如页故障(尝试找到某一页,发现不在内存中,这个故障是可以修复的)、保护故障(访问时发现越界了——只允许在某个内存范围内访问,但却超出了这个范围,导致访问被拒绝)
中断/终止:蓝屏(检查到内存出现问题等)、死机等
每一个异常,都是有对应的编号的。其中,0-31对应了固定的异常,而从32开始则是自定义异常。
常见的同步异常:除数为0异常、保护异常、页故障、机器检测问题、其他异常(从32号开始,都是任意定义的,而32号之前的则是固定的)