9.5 内部中断分类说明

计算机组成

9 中断和异常

9.5 内部中断分类说明

Screen Shot 2018-09-30 at 9.53.53 am

现在,我们已经知道了中断处理的基本过程。那么就来花一点时间,看一看内部中断到底有哪些不同的类型。

我们还是以x86的实模式为例,这个比较简单,但基本原理都是一样的。

Screen Shot 2018-09-30 at 9.54.02 am

在x86的实模式下,我们要来分析的内部中断就是这四个。

Screen Shot 2018-09-30 at 9.54.10 am

这四个内部中断所使用的类型号,分别是0、1、3、4。而加在中间的类型2,是留给外部中断的,这个非屏蔽中断是外部中断的一种。

现在,就分别来看一看这四个内部中断。

Screen Shot 2018-09-30 at 9.54.19 am

首先来看类型0的中断。这个中断是和除法有关的,我们不妨来回顾一下除法器的结构,在这个除法器支持的运算中,有一个64位的被除数和一个32为的除数,运算产生的商和余数,分别放到两个32位寄存器当中去。这样的设置对于多数除法运算来说,都是没有问题的。但难免有一些比较特别的情况,例如除数很小,比如说就是2,而被除数很大。那么一个很大的被除数除以2之后,得到的商就会超过32位,没有办法放进这个商寄存器当中去。当运算器遇到这种情况时,就会产生一个除法错中断,这个中断的类型号是0。所以,CPU就会去中断向量表中取出0号中断向量,然后去执行对应的中断服务程序。

那有一种情况,就是用0作除数,这样得到的商应该是无穷大,肯定超过目标寄存器所能表示的范围。所以,这个除法错中断有时也会被称为除0中断。

Screen Shot 2018-09-30 at 9.54.29 am

然后我们再来看4号中断,这个中断叫作溢出中断。也就是因为算术运算发生了溢出而引起的中断,这个中断的产生要借助一条特殊的指令,也就INTO指令。当执行这条指令时,硬件电路会去检查溢出标志位OF是否位1。如果为1,则会引起类型为4的内部中断。INTO这条指令的格式如图,它是一个没有操作数的指令,比如 add ax,bx 这条加法指令执行时,就有可能发生了溢出,那么运算器在运行完这个加法后,会去设置标志寄存器当中的标志位,也就是第11号溢出标志位,但这个操作本身并不会引发中断,只是将标志位置1。但如果之后执行了INTO指令,这条指令是会去检查OF标志位。如果这时OF标志位为1,那就引起了4号中断;但是如果INTO指令执行时,OF标志位为0,那就什么也不会发生,这条INTO指令就相当于一条空操作指令。所以,INTO指令通常会安排在算术运算指令之后,用来检查这个运算是否发生了溢出。并且,在发生溢出时,就调用中断程序进行处理。因为这是4号中断,所以我们也可以写成INT 4这样的形式。注意INT和4之间有一个空格,这个4是INT指令的操作数。实际上,任何一个类型的中断,都可以采用这样的形式进行调用,用INT指令带上这个中断的类型号,我们在后面还会看到这样的例子。

我们要注意区分的是,这个4号中断和刚才介绍的0号中断,在引起中断的时机上是有区别的。虽然它们都在检查运算时出现的异常情况,但是0号中断是在那条除法指令执行后,立刻发生的,而4号中断则是要在编程时,加入INTO指令进行主动的检查。因为很多时候,这样的加法运算的溢出,并不需要进行处理,如果每一次溢出,都要引发中断,反而可能影响程序的性能。所以,在指令系统设计的时候,就把是否要检查这种溢出的情况,交给程序员来进行判断。

那么类型0和类型4这两个中断,都是和运算结果出现了异常情况有关系的;而另两个内部中断,则是主要用来进行错误调试的。

Screen Shot 2018-09-30 at 9.54.37 am

其中,类型1中断称为单步中断。要引发这个中断,需要将标志寄存器当中的TF位置1,这时CPU就处于单步工作方式。在单步工作方式下,CPU每执行完一条指令,就会自动的产生一个类型1的中断,然后进入类型1中断服务程序。这种工作方式主要是用来进行错误调试的。比如说,你发现CPU执行一段程序有错误,但是又不清楚这个错误具体发生在什么地方,那就可以将TF标志位置为1,在单步工作方式下进行调试。

通常情况下,我们会在这个类型1的中断服务程序当中,将CPU当中的各个寄存器的内容,在屏幕上显示出来,这样CPU每执行一条指令,我们就可以在屏幕上看见CPU当前正在执行的是哪一条指令,这条指令的地址是什么,执行这条指令的前后,那些通用寄存器又有什么样的变化。这样我们就有可能发现,到底在哪一步,发生了不符合我们预期的行为。这个方式对于调试是很有用的,但是CPU每执行完一条指令,就要产生一个中断,程序执行的速度就非常慢了。如果想要调试一个很大的程序,仅用单步中断就会变得比较困难。所以,还有一个用于调试的中断,就是类型3,断点中断。

Screen Shot 2018-09-30 at 9.54.47 am

断点中断通常和单步中断配合使用。在调试一个很大的程序时,一般我们会先通过断点中断,将错误定位在这个程序的一小段代码中,然后,再对这一小段代码用单步的方式进行跟踪调试。这样就可以大大提升调试的效率,这个思想也是很简明的,如果我一个大的程序运算结果出现问题,我们并不会马上从这个程序的开头,一条一条指令的顺序检查,而通常会是将这个程序切成几个大的部分,然后检查每一个部分的结果是否正确。

我们刚才提过,所有的中断都可以用指令的形式来调用,那么用INT加中断类型号的这个形式的指令,都是一个两字节的指令。只有断点中断是一个例外,INT 3指令是一条单字节长的指令,这就是INT 3指令的编码,11001100。

那为什么INT n形式的指令都是两个字节的呢?我们只用想一想这个n要表达多大的范围,我们一共有256个中断类型。所以,这个n要表示0到255,要表示这些数,需要多少个二进制位呢?需要8位,2的8次方就是256。那在前面,还得有一个字节的指令操作码,所以总共是两个字节。

我们为什么要单独给3号中断,设置一个单字节长的指令编码呢?

Screen Shot 2018-09-30 at 9.54.55 am

这和它的使用方式是有关系的。这个断点中断指令的使用并不那么简单,我们要在需要调试的程序当中,选择一个希望中断的位置,然后用这条断点中断指令,去代替这个位置原有的指令。当然,我们需要把原有的这条指令保存起来。这个都是要由调试人员手工来完成的,替换完以后,我们再次运行这个程序,用户程序运行到我们选好的这个中断点的时候,它就执行了INT 3这条指令,从而进入了对应的中断服务程序。我们就可以在这个中断服务程序当中,将CPU的各个寄存器的值都打印在屏幕上,从而判断执行到这个断点的时候,这个用户程序是否还运行正常。如果运行正常,可能我们就需要把这个断点再往后挪一挪;如果这个时候,已经有寄存器的值不符合我们的预期了,我们就需要将断点放到更靠前的位置,进行进一步的检查。但我们还得记得,在这个中断服务程序当中,需要将这个断点位置原有的那条指令的编码再替换回去,并且将指令指针寄存器的值再回退一个字节,也就是指向这个原有的指令,以保证中断返回之后,CPU能从断点的这个地方继续执行。

我们来看一个例子。

Screen Shot 2018-09-30 at 9.55.04 am

假设这是我们要调试的一段程序。这里有5条指令,左边是它们对应的指令的地址,其中有一些是两个字节的指令,有一些是一个字节的指令。如果我们想选择这条 inc al 指令作为断点,那么就需要把这条指令的编码,替换成 int 3 指令的编码。这时候就体现出了 int 3 这条指令是一个单字节指令的好处。因为x86的指令当中,最短的就是一个字节的指令,像这条 inc al 指令。如果断点中断指令是一个两字节的指令,那么在替换进来之后,就会影响到后续的指令,而后续的指令,却有可能在这个断点之前执行。比如说就像这段程序代码,在这个断点之前,就有一个转移指令,直接跳到了断点之后,然后经过条件判断,可能又跳转回来,才继续执行到断点的地方。所以,我们将这条 inc al 指令替换成断点中断指令的时候,一定不能影响后续的指令,这也就是为什么断点中断指令必须要是一个字节的。那么在这段程序执行的过程中,如果这个条件(转移)已经发生了,就会运行到断点中断指令,然后CPU内部就会发生中断,转而去执行3号中断向量所对应的中断服务程序。在这个中断服务程序中,我们就可以把AL寄存器的内容打出来,这样调试人员就可以观察到,这个时候AL寄存器的内容,是否符合我们的预期了,如果我们发现AL寄存器的内容有错误,那么就可以再次运行这个程序,并在附近的位置设置CPU进入单步工作模式,进行单步调试。这样就会比较容易的发现一些隐藏的很深的错误。

Screen Shot 2018-09-30 at 9.55.15 am

这些内部中断都有共同的特点。

首先,它们的中断类型号是由CPU内部产生的。因为这些异常的情况,就是CPU自己在执行指令的过程中发生的。所以,它是知道到底发生的是什么类型的中断。而我们后面要介绍的外部中断,则有可能来自不同的外部设备。所以,CPU需要去读取外设,以得知中断类型号。这是第一个区别。

第二个区别,是屏蔽的方式。在内部中断当中,除了单步中断以外,都不可以用软件的方式来进行屏蔽。也就是,我们不可以通过设置IF这个标志位,来让CPU不响应内部中断。

第三个是优先级。也就是内部中断和外部中断同时发生时,CPU先处理哪个中断。那么除了单步中断以外,所有的内部中断优先级都比外部中断高。CPU总是优先处理自己内部发生的异常情况。

Screen Shot 2018-09-30 at 9.55.23 am

现在,我们已经了解了内部中断的基本类型。有两个是用来处理运算的异常情况,还有两个是CPU用来调试的。那后来,随着内部中断的类型不断的增加,其中增长(zhang)的大部分,都是CPU用来调试和管理用的中断。

posted @ 2018-10-04 21:24  houhaibushihai  阅读(1707)  评论(0编辑  收藏  举报