1 概览
完好的程序都满足以下特征
- 自动运行
我们的程序和指令都是一条条顺序执行,不需要通过键盘或者网络给这个程序任何输入 - 正常运行
没有遇到计算溢出之类的程序错误。
不过,现实的软件世界可没有这么简单
- 程序不仅是简单的执行指令,更多的还需要和外部的输入输出打交道
- 程序在执行过程中,还会遇到各种异常情况,比如除以0、 溢出,甚至我们自己也可以让程序抛出异常。
遇到这些情况,计算机是怎么运转的呢,也就是说,计算机究竟是如何处理异常的
2 异常:硬件、系统和应用的组合拳
2.1 软件 还是 硬件 异常?
一提到异常 (Exception),可能你的第一反应就是Java中的Exception。 不过我们今天讲的,并不是这些软件开发过程中遇到的“软件异常”
而是和硬件、系统相关 的“硬件异常”。
当然,“软件异常”和“硬件异常”并不是业界使用的专有名词,只是我为了方便给你说明,和Java中软件抛出的Exception进行的人为区分,你明白这个意思就好。
尽管,这里我把这些硬件和系统相关的异常,叫作“硬件异常”。但是,实际上,这些异常,既有来自硬件的,也有来自软件层面的。
比如,我们在
- 硬件层面
当加法器进行两个数相加的时候,会遇到算术溢出
或者,你在玩游戏的时候,按下键盘发送了一个信号给到CPU,CPU要去执行一个现有流程之外的指令,这也是 一个“异常”
同样,来自
- 软件层面
比如我们的程序进行系统调用,发起一个读文件的请求。这样应用程序向系统调用发起请求的情况,一样是通过“异常”来实现的。
2.2 异常的一生
异常, 其实是一个硬件和软件组合到一起的处理过程。
- 异常的前半生
异常的发生和捕捉,在硬件层面完成 - 异常的后半生
异常的处理,其实是由软件来完成的!
2.3 异常代码
计算机会为每一种可能会发生的异常,分配一个异常代码(Exception Number)
异常代码也叫作中断向量(Interrupt Vector)。
异常发生的时候,通常是CPU检测到了一个特殊的信号。
比如
- 你按下键盘上的按键,输入设备就会给CPU发一个信号
- 正在执行的指令发生了加法溢出,同样,我们可以有一个进位溢出的信号
这些信号呢,在组成原理,一般叫发生了一个事件(Event)
CPU在检测到事件的时候,其实也就拿到了对应的异常代码。
这些异常代码里
- I/O发出的信号的异常代码,是由操作系统来分配的,也就是由软件来设定的
- 像加法溢出这样的异常代码,则是由CPU预先分配好的,也就是由硬件来分配的. 这又是另一个软件和硬件共同组合来处理异常的过程
拿到异常代码之后,CPU就会触发异常处理的流程
计算机在内存里,会保留一个异常表 (Exception Table)。
也叫中断向量表(Interrupt Vector Table),好和上面的中断向量对应起来。
这个异常表有点儿像我们在之前的GOT表,存放的是不同的异常代码对应的异常处理程序(Exception Handler)所在的地址
2.4 异常处理程序流程
我们的CPU在拿到了异常码后
- 首先, 把当前的程序执行的现场,保存到程序栈
- 然后, 根据异常码查询,找到对应的异常处理程序
- 最后, 把后续指令执行的指挥权,交给这个异常处理程序
这样“检测异常 => 拿到异常码 => 再根据异常码进行查表处理”的模式,在日常开发的过程中是很常 见的。
比如说
Web或者App开发
通常都是前后端分离的
- 前端应用,会向后端发起HTTP请求
- 当后端遇到了异常,通常会给到前端一个对应的错误代码
- 前端的应用根据这个错误代码
- 在应用层面去进行错误处理
- 在不能处理的时候,它会根据错误代码向用户显示错误信息。
Java里面
可以设定ExceptionHandler,来处理线程执行中的异常情况
public class LastChanceHandler implements Thread.UncaughtExceptionHandler { @Override public void uncaughtException(Thread t, Throwable e) { // do something here - log to file and upload to // server/close resources/delete files... } } Thread.setDefaultUncaughtExceptionHandler(new LastChanceHandler());
使用一个线程池去运行调度任务的时候
可以指定一个异常处理程序。
对于各个线程在执行任务出现的异常情况,我们是通过异常处理程序进行处理,而不是在实际的任务代码里处理。
这样,我们就把业务处理代码就和异常处理代码的流程分开了。