1 概览完好的程序都满足以下特征
不过,现实的软件世界可没有这么简单
遇到这些情况,计算机是怎么运转的呢,也就是说,计算机究竟是如何处理异常的 2 异常:硬件、系统和应用的组合拳2.1 软件 还是 硬件 异常? 一提到异常 (Exception),可能你的第一反应就是Java中的Exception。 不过我们今天讲的,并不是这些软件开发过程中遇到的“软件异常” 而是和硬件、系统相关 的“硬件异常”。 当然,“软件异常”和“硬件异常”并不是业界使用的专有名词,只是我为了方便给你说明,和Java中软件抛出的Exception进行的人为区分,你明白这个意思就好。 尽管,这里我把这些硬件和系统相关的异常,叫作“硬件异常”。但是,实际上,这些异常,既有来自硬件的,也有来自软件层面的。 比如,我们在
同样,来自
2.2 异常的一生 异常, 其实是一个硬件和软件组合到一起的处理过程。
2.3 异常代码 计算机会为每一种可能会发生的异常,分配一个异常代码(Exception Number) 异常代码也叫作中断向量(Interrupt Vector)。 异常发生的时候,通常是CPU检测到了一个特殊的信号。 比如
这些信号呢,在组成原理,一般叫发生了一个事件(Event) CPU在检测到事件的时候,其实也就拿到了对应的异常代码。 这些异常代码里
拿到异常代码之后,CPU就会触发异常处理的流程 计算机在内存里,会保留一个异常表 (Exception Table)。 也叫中断向量表(Interrupt Vector Table),好和上面的中断向量对应起来。 这个异常表有点儿像我们在之前的GOT表,存放的是不同的异常代码对应的异常处理程序(Exception Handler)所在的地址 2.4 异常处理程序流程 我们的CPU在拿到了异常码后
这样“检测异常 => 拿到异常码 => 再根据异常码进行查表处理”的模式,在日常开发的过程中是很常 见的。 flowchatst=>start: 开始e=>end: 结束op1=>operation: 检测异常op2=>operation: 拿到异常码op3=>operation: 再根据异常码进行查表处理st->op1->op2->op3op3->e 比如说 Web或者App开发 通常都是前后端分离的
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()); 使用一个线程池去运行调度任务的时候 可以指定一个异常处理程序。 对于各个线程在执行任务出现的异常情况,我们是通过异常处理程序进行处理,而不是在实际的任务代码里处理。 这样,我们就把业务处理代码就和异常处理代码的流程分开了。 3 异常的分类异常可以由硬件触发,也可以由软件触发 3.1 中断(Interrupt) 顾名思义,就是程序在执行到一半的时候,被打断了。这个打断执行的信号,来自于CPU外部的I/O设备。 你在键盘上按下一个按键,就会对应触发一个 相应的信号到达CPU里面。CPU里面某个开关的值发生了变化,也就触发了一个中断类型的异常。 3.2 陷阱(Trap) 程序员“故意“主动触发的异常。就好像你在程序里面打了一个断点,这个断点就是设下的一个'陷阱'。 当程序的指令执行到这个位置的时候,就掉到了这个陷阱当中。然后,对应的异常处理程序就会来处理这个'陷阱'当中的猎物。 最常见的一类陷阱,应用程序调用系统调用的时候,也就是从用户态切换到内核态的时候。
应用程序通过系统调用去读取文件、创建进程,其实也是通过触发一次陷阱来进行的。这是因为用户态的应用程序没有权限来做这些事情,需要把对应的流程转交给有权限的异常处理程序来进行。 3.3 故障(Fault) 陷阱是我们开发程序的时候刻意触发的异常,而故障通常不是。 比如,我们在程序执行的过程中,进行加法计算发生了溢出,其实就是故障类型的异常。 这个异常不是我们在开发的时候计划内的,也一样需要有对应的异常处理程序去处理。 故障和陷阱、中断的重要区别 故障在异常程序处理完成之后,仍然回来处理当前的指 令,而不是去执行程序中的下一条指令。 因为当前的指令因为故障的原因并没有成功执行完成。 3.4 中止(Abort) 与其说这是一种异常类型,不如说这是故障的一种特殊情况。 当CPU遇到了故障,但是恢复不过来的时候,程序就不得不中止了。 3.5小结 中断异常的信号来自系统外部,而不是在程序自己执行的过程中,所以我们称之为“异步”类型的异常。 而陷阱、故障以及中止类型的异常,是在程序执行的过程中发生的,所 以我们称之为“同步“类型的异常。 在处理异常的过程当中,无论是异步的中断,还是同步的陷阱和故障,我们都是采用同一套处理流程,也就是上面所说的,“保存现场、异常代码查询、异常处理程序调用“。 而中止类型的异常,其实是在故障类型异常的一种特殊情况。当故障发生,但是我们发现没有异常处理程序能够处理这种异常的情况下,程序就不得不进入中止状态,也就是最终会退出当前的程序执行。 4 异常的处理:上下文切换在实际的异常处理程序执行之前,CPU需要去做一次“保存现场”的操作。这个保存现场的操作, 和函数调用的过程非常相似。 切换到异常处理程序,就好像是去调用一个异常处理函数。指令的控制权被切换到了另外一个'函数',所以我们自然要把当前正在执行的指令去压栈。 这样才能在异常处理程序执行完后,重新回到当前的指令继续往下执行。 不过,切换到异常处理程序,比起函数调用,还是要更复杂一些。原因有下面几点
所以,对于异常这样的处理流程,不像是顺序执行的指令间的函数调用关系。而是更像两个不同的独立进程之间在CPU层面的切换,所以这个过程我们称之为上下文切换(Context Switch)。 5 总结计算机里的“异常”处理流程。这里的异常可以分成中断、陷阱、故障、中止 这样四种情况。这四种异常,分别对应着I/O设备的输入、程序主动触发的状态切换、异常情况下的程序出错以及出错之后无可挽回的退出程序。 当CPU遭遇了异常的时候,计算机就需要有相应的应对措施。CPU会通过“查表法”来解决这个问 题。在硬件层面和操作系统层面,各自定义了所有CPU可能会遇到的异常代码,并且通过这个异 常代码,在异常表里面查询相应的异常处理程序。在捕捉异常的时候,我们的硬件CPU在进行相 应的操作,而在处理异常层面,则是由作为软件的异常处理程序进行相应的操作。 而在实际处理异常之前,计算机需要先去做一个“保留现场”的操作。有了这个操作,我们才能在异常处理完成之后,重新回到之前执行的指令序列里面来。这个保留现场的操作,和我们之前讲 解指令的函数调用很像。但是,因为“异常”和函数调用有一个很大的不同,那就是它的发生时间。函数调用的压栈操作我们在写程序的时候完全能够知道,而“异常”发生的时间却很不确定。 所以,“异常”发生的时候,我们称之为发生了一次“上下文切换”(Context Switch)。这个时 候,除了普通需要压栈的数据外,计算机还需要把所有寄存器信息都存储到栈里面去。 |
|