801-中断和异常的来源
# 801-中断和异常的来源
大家好,我就是一个CPU, 那我的任务呢就是进行运算,有人交给我了一个本,上面写了很多的算式,让我进行运算, 好,那我就开始算了,上面写着3*4,我一算是12,填在后面的空格里, 还有呢15+15,等于30,卸载后面的空格里。很好。接着运算下一条。 1234567*7654321,这个一算, 结果好长啊,后面这个空格只有这么大,填不下,这可怎么办呢?
早期的电子计算机功能非常明确,那就是进行数学运算。 但是如果运算出现了异常情况那应该怎么办呢? 因为计算机当中都只能提供有限的单元来表示一个数,所以有时候就会出现运算结果过大,无法全部保存的情况,这就叫做溢出。 如果从这个模型机上来看,溢出就会发生在ALU这个运算单元上。 这个运算单元的源操作数要么来自于寄存器,要么来自于内存。 我们假设都是32位的,对它们进行运算,比如说进行加法, 那么运算的结果就有可能超过32位。
那么假设在内存当中的某一条指令是这条运算指令发生了溢出, 那么CPU能怎么办呢?运算发生了溢出,CPU的硬件电路是很容易检测到的。 但问题就在于检测到了溢出之后应该怎么办? 如果完全用硬件电路来处理溢出,那样不够灵活。 一旦CPU制造出来我们就没办法改变这个电路,从而没有办法修改处理溢出的方法, 所以最好还是用软件的方法来解决。
那么来看看早期的计算机是怎么来解决这个异常的情况。 这是UNIVAC,也就说ENIAC的主要设计者爱科特和莫克丽在离开宾夕法尼亚大学独立创办公司之后设计的那台计算机。 这是第一个带有异常处理的计算机系统。 如果它在进行运算时发生了异常情况,比如算术运算溢出, 它就会转向地址0,执行两条修复指令。 这个方法的思想是非常重要的。不然的话,一旦出现异常的情况,就只能停机, 然后等人工来解决问题以后才能继续运行。
而如果我们能在地址0这里存放几条指令,用于修复这个异常的情况, 从而就有可能在修复完成之后继续执行刚才的运算。 这样就可以避免停机。 而且如果之后发现这样的修复方法不太合适,那 只需要通过修改存放在地址0的这个地方的指令就可以。用这样的方法来处理异常就非常的灵活和便利。
而这种思想也一直延续到现在。现代计算机的CPU处理异常从根本上来说也是采用了这个方法。
那么结合我们现在讲的CPU的结构,来看看这个方法的简单原理。 那在ALU进行运算完成之后, 硬件电路就很容易检测出这次运算是否发生了溢出, 所以我们可以在ALU这里增加一根信号线,连到PC的更新部件, 那原先这个PC的更新部件会从几个来源对PC进行选择, 要么增加一个固定的值,比如说PC+4, 要么是根据转移目标地址产生一个新的PC。
那么现在增加一种新的情况,就是当运算溢出的信号有效时,这个部件就固定产生0,送到PC寄存器, 这样在下一个周期,PC寄存器就会从地址0取出指令进行执行。 那么执行若干条指令之后,就可以修复这个异常带来的影响,或者至少标记出了这次运算时错误的,以免对后续造成影响。
那如果在通过一些方式,可以记住刚才发生异常时的PC的值, 那就可以再通过PC更新的部件,将这个值装入PC寄存器当中去,从而从刚才发生异常的地方开始继续执行。 这就是一个简单的处理运算结果异常的方式。
不过后来的计算机就不仅仅是只进行数学运算,还要与外界进行很多的交互,我们现在的计算机当中有很多的输入输出的部件,那当CPU在运行程序的时候, 有可能我们就会在键盘上一个键,或者网络上就会来了一个数据包, 这些输入输出的操作都需要CPU中断现有程序的运行来进行处理, 那么这样的需求其实在很早也就有了。
1954年的DYSEAC是第一个带有外部终端处理的系统。 这台计算机也可以说是最早的移动计算设备。 说道移动计算设备,你可能会想到笔记本电脑,或者平板电脑。不过这台计算机的移动性和他们有点不太一样, 它是要挂在卡车上移动的。
不过它确实是在移动着进行计算。 和UNEVAC处理异常的方式不太一样,它设置了两个程序计数器, 然后根据外部输入输出设备的信号,可以在这两个程序计数器之间进行切换, 这样它就可以交叉执行两端不同的程序, 可以一段程序是运算的功能,而另一端程序是专门用于处理外部的中断。
那后来UNIVAC也增加了对外部中断的处理。 用于对风洞数据的实时收集
那么简而言之,刚才提到的场景都是CPU在运行的时候遇到的一些事件, 这些事件有可能是线性程序本身出现的,那也有可能是在CPU的外部甚至是整个系统的外部出现的事件。那么这时就要求CPU强行中止现在正在运行的程序。 并且从一个新的存储器区域启动相应的程序去处理这些事件。
还有一点也很重要,就是在处理完这些事件之后,CPU需要恢复到原来的程序继续运行。 那这些事件就被称为中断或者异常。 那究竟中断和异常又有什么区别呢? 那其实在这件事情上也没有明确的定论, 不同的体系结构,不同的书籍,不同的研究群体,对它们的定义都有区别。 其命名方式大体上可以分为这么几类。 一种是把CPU内部运行程序产生的这些事件称为内部中断,而把CPU外部那些输入输出设备产生的事件称为外部中断。 而这些所有的事件又统称为中断。
而第二种方式则是将CPU运行程序所引发的特殊的事件称为软件中断, 而把外部的这些输入输出设备产生的事件称为硬件中断。 这种方式就是把运行程序看做一种软件的行为, 而把外设的工作则归为一种硬件的行为。
还有一种常见的方式就是将外部设备发生的这些事件 称为中断,而将CPU内部运行程序发生的这些事件称为异常。 这也是一种常见的命名方式。 但是对这些事件的统称,也是有不同的表示方式。 有一些把中断和异常统称为中断,也有些地方把中断和异常统称为异常。 那么这些不同的名词时从不同的角度来理解和表述这件事情, 但它们所代表的内部事件和外部事件的具体行为都是一样的。 在我们的课中将采用中断这种统称,而在具体划分上主要采用外部中断和内部中断的表述方式, 但也会根据不同的场景有时也使用其他的表述方式。这请大家注意。
那好了,现在制造这本手册的人告诉我了, 如果你发现运算的结果在这空格里填不下的话, 那也不要着急,就翻到这个手册的最前面,啊,这个手册最前面的第一行, 就写了应该怎么处理这种情况,照着这个往下做,就可以了。 那这样事情就很简单了。 但是随着我这个运算任务越来越多,我可能会遇到新的问题。 突然发现不是空格写不下了,这个字母我不认识, 这个运算符我没见过, 那应该怎么办呢?