异常和中断:程序出错了怎么办

文章目录

    • 异常:硬件、系统和应用的组合拳
    • 异常的分类:中断、陷阱、故障和中止
        • 中断
        • 陷阱
        • 故障
        • 中止
    • 异常的处理:上下文切换
    • 总结

自动运行且正常运行的程序

  • 自动运行:程序和指令都是一条条顺序执行,没有键盘和 络的任何输入
  • 正常运行:我们的程序都是能够正常执行下去的,没有遇到计算溢出之类的程序错误

异常:硬件、系统和应用的组合拳

这里说的异常和C++或者Java里的Exception不一样。

硬件和系统相关的异常既有来自于硬件的,也有来自于软件的。

  • 硬件层面:算术相加溢出和玩游戏的时候按下键盘发送一个信 给CPU,也是一个异常。
  • 软件层面:程序进行系统调用,发起一个读文件的请求,应用程序向系统调用发起请求的情况,也是通过异常来实现的

异常是一个硬件和软件组合到一起的处理过程

  • 异常的发生和捕捉是在硬件层面完成的。
  • 异常的处理是由软件来完成的。

异常代码和异常处理

  • 计算机会为每一种可能的异常分配一个异常代码,异常代码也叫做中断向量。
  • 异常发生时,通常是CPU检测到了一个特殊的信 ,加法溢出信 和键盘输入信 叫做发生了一个事件。
  • CPU在检测事件的时候,其实也就拿到了对应的异常代码。
    • 异常代码里,IO发生的信 的异常代码,是由操作系统来分配的,也就是由软件来设定的。
    • 加法溢出这样的异常代码,是由CPU预先分配的,是由硬件来分配的。
  • 拿到异常代码之后,CPU就会触发异常处理的流程,在内存里,会保留一个异常表,也叫做中断向量表,存放的是不同的异常代码对应的异常处理程序所在的地址。

Java 里面,我们使用一个线程池去运行调度任务的时候,可以指定一个异常处理程序。

对于各个线程在执行任务出现的异常情况,我们是通过异常处理程序进行处理,而不是在实际的任务代码里处理。这样,我们就把业务处理代码就和异常处理代码的流程分开了。

异常的分类:中断、陷阱、故障和中止

中断

程序执行到一半的时候,被打断了,这个执行打断的信 ,来自于CPU外部的IO设备,键盘下按下一个按键,会触发一个相应的信 到CPU,CPU打开一个开关,就触发了一个中断类型的异常。

陷阱

程序员故意主动触发的异常,比如在程序里打了一断点,这个断点就是设下的一个陷阱,当程序执行到这个位置的时候,就掉到了这个陷阱中,对应的异常处理程序就会处理这个陷阱当中的猎物。

最常见的一类陷阱,发生在我们应用程序调用系统调用的时候,也就是从程序的用户态切换到内核态的时候。

应用程序通过系统调用去读取文件、创建进程,都是通过触发一次陷阱来进行的,这个是因为用户态的应用程序没有权限,需要把对应的流程,交给有权限的异常处理程序来进行。

故障

和陷阱的区别在于,陷阱是我们开发程序的时候刻意触发的异常,而故障不是。加法溢出就是故障类型的异常,不是开发计划内的。

故障和陷阱、中断的一个重要区别就是,故障在异常处理完成之后,仍然回来处理当前的指令,而不是去执行程序中的下一条指令。因为当前指令因为故障的原因并没有执行完成。

中止

是一种故障的特殊情况,CPU遇到了故障,恢复不过来的时候,程序就不得不终止了。

异常和中断:程序出错了怎么办
  • 四种异常里,中断异常的信 来自于系统外部,而不是程序自己执行的过程中,所以我们称之为异步类型的异常。
  • 陷阱、故障、以及中止类型的异常,是在程序执行的过程中发生的,称之为同步类型的异常。

处理异常的过程当中,无论是异步的中断,还是同步的陷阱、故障,都是采用“保存现场、异常代码查询、异常处理程序调用”,中止类型的异常,其实是故障类型异常的一种特殊情况。

当故障发生我们没有异常处理程序能够处理这种异常的情况下,程序就不得不进入中止状态,也就是最终会退出当前的程序执行。

异常的处理:上下文切换

异常处理程序之前,CPU需要去做一次保存现场的操作,切换异常处理程序的时候,就好像是调用一个异常处理函数,指令的控制权被切换到另外一个函数,就需要把正在执行的指令去压栈,这样才能在异常处理程序执行完成之后,重新回到当前指令继续往下执行。

切换到异常处理程序,比函数调用要复杂一些

  • 因为异常情况往往发生在程序正常执行的预期之外,比如中断、故障发生的时候。所以本来程序要压栈要做的事情,还需要把CPU内当前运行程序所用到的寄存器都放到栈里面,典型的就是条件码寄存器里面的内容。
  • 像陷阱这样的异常,设计程序指令再用户态和内核态之间的切换。对应压栈的时候,对应的数据是压到内核栈里,而不是程序栈里。
  • 故障这样类型的异常,在异常处理程序执行完成之后,从栈里返回处理啊,继续执行的不是顺序的下一条指令,而是故障发生的当前指令。因为当前指令因为故障没有正常执行成功,必须重新执行一次。

对于异常这样的处理流程,不像是顺序执行的指令间的函数调动关系,而更像两个不同进程之间在CPU层面的切换,所以这个过程我们称之为上下文切换

总结

这一讲,我给你讲了计算机里的“异常”处理流程。这里的异常可以分成中断、陷阱、故障、中止这样四种情况。这四种异常,分别对应着 I/O 设备的输入、程序主动触发的状态切换、异常情况下的程序出错以及出错之后无可挽回的退出程序。

当 CPU 遭遇了异常的时候,计算机就需要有相应的应对措施。CPU 会通过“查表法”来解决这个问题。在硬件层面和操作系统层面,各自定义了所有 CPU 可能会遇到的异常代码,并且通过这个异常代码,在异常表里面查询相应的异常处理程序。在捕捉异常的时候,我们的硬件 CPU 在进行相应的操作,而在处理异常层面,则是由作为软件的异常处理程序进行相应的操作。

而在实际处理异常之前,计算机需要先去做一个“保留现场”的操作。有了这个操作,我们才能在异常处理完成之后,重新回到之前执行的指令序列里面来。这个保留现场的操作,和我们之前讲解指令的函数调用很像。但是,因为“异常”和函数调用有一个很大的不同,那就是它的发生时间。函数调用的压栈操作我们在写程序的时候完全能够知道,而“异常”发生的时间却很不确定。所以,“异常”发生的时候,我们称之为发生了一次“上下文切换”(Context Switch)。这个时候,除了普通需要压栈的数据外,计算机还需要把所有寄存器信息都存储到栈里面去。

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2021年1月10日
下一篇 2021年1月10日

相关推荐