804-中断的处理过程
# 804-中断的处理过程
那我们现在可以放心大胆地进行运算了。 算,算,算。一旦遇到了一个异常情况呢,我们就根据这个异常情况的类型, 去查找这个手册最前面的表格。上面可能是第四种类型,我们找到第四条, 上面写着操作方法在第十二页。 第十二页,好,找到了。那么按照这个操作方法呢一步一步往下执行, 就把这问题给解决了。然后呢,我们就可以继续刚才的运算了,对不对? 可是我刚才是在哪一页进行的运算? 这在哪儿呢?我怎么回到刚才的运算呢?
我们先来看一看在CPU内部是如何检测中断的。 我们就以x86的实模式为例。在CPU内部, 会有中断的处理电路。如果在运算时发生了异常的情况, 比如出现除法错误,那运算器就会产生相应的中断信号。 那如果遇到其他指令产生的中断,就会产生其他对应的控制信号。 那这些中断都是和正在运行的程序本身有关的, 所以我们叫它软件中断,或者是内部中断。 那还有来自CPU外部的中断。 这些外设有可能是键盘、网络、打印机,等等。 那当键盘按键,或者有收发网络包的情况, 这些外设的芯片或者板卡就会通过主板上的物理连线,发出中断请求信号, 这些信号最终都会连接到CPU内部。 不管来源是哪里,CPU检测到了中断请求信号,就会进行处理。
中断处理的过程是这样的:
首先,CPU会关闭中断响应,也就是不再接受后面其他的外部中断请求了。 注意,只是不接收外部中断请求。
然后将发生中断处的这个指令的地址保存到栈中,也就是内存当中的一个区域, 这个信息必须要保存好,以便于处理完中断后可以正确地返回当前的程序继续执行。 那究竟是保存发生中断的这条指令的地址, 还是发生中断的这条指令之后的一条指令的地址, 和这个中断具体的类型会有关系。
第三步则由CPU识别中断的来源, 确定中断类型号,从而能够找到相应的中断服务程序的入口地址。 这三步一般都是由硬件自动完成的。
第四步是保存现场, 也就将中断服务程序中可能会改变的寄存器先压栈,也就是放到内存中保存起来。
为什么要这么做呢?我们来看一个例子。在这个主程序片段中, 我们假设执行到这条触发指令的时候发生了中断, 那这时就会调用中断服务程序。 我们假设在这中断服务程序当中,有这么一条指令, 它会改写CX寄存器的值。 那如果我们没有对CX寄存器进行保护的话,在中断返回之后,CX寄存器的内容就已经改变了。 然后回到主程序继续往下执行时,这个CX寄存器的内容,就不再是主程序中之前传到CX寄存器中的内容了。 这样就会导致主程序的错误。
所以在中断服务程序中,如果要用到CX寄存器, 就应该先进行压栈,把当前CX寄存器的值保存到存储器中, 然后在中断返回之前,再执行弹栈的操作。这样中断返回之后, CX寄存器的内容就没有变化,不会影响主程序的运行。
然后第五步,就是执行这个中断服务程序的主体内容。 而且在中断服务程序中,可以在适当的时候重新开放中断,以便响应其他高优先级的外部中断。以免中断服务程序本身要执行比较长的时间,造成有高优先级的外部中断长时间无法得到响应
那如何在中断服务程序当中,重新开放中断呢? 那这就涉及到对标志寄存器的操作。在标志寄存器当中有一些标志位是状态标志。 状态标志一般情况下,是由硬件设置,然后由软件读取。 例如进位标志,就是在运算器产生进位时,由硬件自动设置, 然后由软件的指令读取出来,可能会进行相关的累加操作。 而另一类标志称为控制标志,这些标志通常是由软件进行设置,然后硬件电路根据这些标志设置的不同, 而执行不同的功能。
那么标志寄存器的第九位就是中断标志。 这个标志控制对可屏蔽中断的响应。那外部中断分为两大类,一类是可屏蔽中断,一类是非屏蔽中断。 IF标志只对外部中断当中的可屏蔽中断起作用。 如果IF等于1,那就允许CPU响应可屏蔽中断请求; 如果IF等于0,那就不允许CPU响应可屏蔽中断请求。 那怎么设置IF标志位的值呢?有两条指令。STI指令就是把IF位置为1, 而CLI指令就是把IF标志位清零。 这两条指令都是没有操作数的指令。 当然,IF指令对非屏蔽中断和内部中断都是不起作用的。
然后我们来看中断处理过程的最后一步,那就是恢复现场并且返回主程序继续运行。
那返回主程序就需要执行中断返回指令。 中断返回指令它可以不带操作数, 那么这条指令的操作,就是从当前的栈顶弹出三个字, 分别送到IP、CS和FLAGS寄存器当中去。 那么这条指令是放在中断服务程序的末尾, 那么在中断调用时,CPU中的硬件电路会将这三个寄存器的值压入栈中,所以在执行中断返回指令时, 也会由硬件从栈顶弹出这三个字,再放回到这三个寄存器当中。 那当CS和IP寄存器改变之后, 下一条指令就会回到程序发生中断的地方继续执行了。
当然我们也注意到,这条中断返回指令操作的是CS和IP寄存器, 那在32位和64位的x86中,这个指令指针寄存器,又被扩展为EIP和RIP寄存器, 它们的宽度都是不一样的,所以后来又有了不同的对应的指令。
我们再用一个图示来看一看刚才介绍的中断处理的过程。 我们就以内部中断为例。当CPU执行到某条指令, 例如就是这条指令,如果此时发生中断,CPU内部就会产生中断信号, 相关的中断处理电路会判断中断的来源,并产生中断类型号。 CPU的硬件电路会将CS和IP寄存器压栈, 这样就保存好了处理完中断后要返回的地址。 同时硬件上还会将FLAGS寄存器压栈,以便保存好当前的各项标志, 以免中断处理程序当中,有些指令会改变程序的标志位。 在硬件上还会清除IF标志位,以起到关中断的作用。
然后根据中断类型号,找到对应的中断向量,也就是新的CS和IP的值, 并以此更新CPU当中的CS和IP寄存器。
当完成这个操作后,CPU就会转到中断服务程序开始执行。 那么在中断服务程序中,也可以执行STI指令,以开放中断。
当完成了中断服务程序之后, 最后一条就是执行中断返回指令。 这条指令会从存储器当中将刚才压栈的三个字弹出来, 并按照对应的顺序,存到CS、IP和FLAGS寄存器当中去, 这样就完成了返回主程序的动作。
这就是中断处理过程的六个主要的步骤。通常情况下前三步都是由处理中断的硬件电路来完成, 后三步则是由软件,也就是中断服务程序来完成。 但这只是一个大体的分工,在真的要涉及中断服务程序的时候, 必须要针对具体的系统,弄清楚软硬件的分工究竟是怎么样的。 例如在保存现场这件事情上,刚才我们介绍的,标志寄存器是由硬件来负责保存的, 但是在另外的一些系统上,可能硬件就不会自动地保存标志寄存器, 就需要由中断服务程序的软件来进行保存。 那么在设计中断服务程序时,必须要搞清楚这些问题,不然就有可能发生错误。
那现在呢,我们不但有了这张表,而且有了一个规范的操作流程, 我们知道从运算中出现异常情况,怎么查到这张表,怎么找到异常的操作的步骤, 然后呢应该事先做哪些记录,以免丢失信息。 最后我们还知道怎么回到我们刚才运算的那个地方, 继续进行下一步的操作。 这就是一个完整的中断的处理过程。