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 寄存器当中去, 这样就完成了返回主程序的动作。
这就是中断处理过程的六个主要的步骤。通常情况下前三步都是由处理中断的硬件电路来完成, 后三步则是由软件,也就是中断服务程序来完成。 但这只是一个大体的分工,在真的要涉及中断服务程序的时候, 必须要针对具体的系统,弄清楚软硬件的分工究竟是怎么样的。 例如在保存现场这件事情上,刚才我们介绍的,标志寄存器是由硬件来负责保存的, 但是在另外的一些系统上,可能硬件就不会自动地保存标志寄存器, 就需要由中断服务程序的软件来进行保存。 那么在设计中断服务程序时,必须要搞清楚这些问题,不然就有可能发生错误。
那现在呢,我们不但有了这张表,而且有了一个规范的操作流程, 我们知道从运算中出现异常情况,怎么查到这张表,怎么找到异常的操作的步骤, 然后呢应该事先做哪些记录,以免丢失信息。 最后我们还知道怎么回到我们刚才运算的那个地方, 继续进行下一步的操作。 这就是一个完整的中断的处理过程。