905-外部中断的处理过程
# 905-外部中断的处理过程
我们之前已经学习了内部中断的处理方法, 那么外部中断和内部中断总体上是很类似的。 只是在一些具体的处理细节上和硬件的连接方式上有所不同。 那我们现在就来看一看外部中断是如何处理的。
外部中断,也叫做硬件中断。 这是由 CPU 外部的中断请求信号启动的中断。以 x86 CPU 为例, 连到外部的中断请求信号一共有两个。一个信号叫做 NMI, 这就是“非屏蔽中断”的缩写。 另一个信号叫 INTR,这就是“中断”这个词的缩写。 相对于非屏蔽中断,我们一般也称它为可屏蔽中断。 不光是 x86,其他很多别的体系结构的 CPU 也往往提供这两种类型的中断引脚。
这两个信号,是对应了 CPU 上真实存在的两个管脚的。 来自外设的中断请求信号就可以通过主板上的连线,连到这两个管脚上。 这个 NMI,也就是非屏蔽中断,一般会连接一个 在这个系统当中非常重要,不希望被屏蔽的中断请求信号。 那至于什么是“非常重要”,就取决于系统设计者的观点。 例如在某些计算机当中,会将表示电池即将没电的这个信号连接到 NMI 上。 电池马上就要没电了,确实是一个非常紧急的情况。因为非屏蔽中断是不受中断允许标志的影响, 即使 CPU 现在将 IF 标志位设为 0,关闭对外部中断的响应, CPU 仍然会响应这个 NMI 的中断请求。 那这样就可以调用中断服务程序, 在断电之前把一些重要的信息保存到硬盘上去。 当然不同的系统可能会连接不同的中断请求信号到 NMI 上, 但都会是一些非常重大,不处理就会导致严重错误的事件。
一般的外设,它的中断请求都会连接到可屏蔽中断上, 但是 CPU 的可屏蔽中断信号的请求输入只有一根, 那就需要通过一些转换电路。现在计算机当中比较常见的是使用中断控制器这个芯片。 中断控制器会将外设输出的中断请求信号作为它的输入连接进来, 然后输出一根信号连接到 CPU 的可屏蔽中断请求信号上。 这个中断控制器也可以看作是一个 I/O 接口, 它内部也有一些被称为 I/O 端口的寄存器, CPU 可以访问这些端口,对中断控制器进行配置,例如可以配置这些外设的中断请求,哪个优先级高,哪个优先级低, 或者可以在这些中断请求当中,屏蔽其中的一部分。 这些都是中断控制器的基本功能。
因为这样的中断控制器是可以由编写程序进行配置的,所以又称为可编程中断控制器,简称 PIC。 有一个广泛使用的可编程中断控制器,就是英特尔的 8259。 我们可以看到 8259 上,从 IR0 一直到 IR7, 一共有 8 个中断请求的输入可以用来连接外设的中断请求信号, 然后它还有一些地址和数据信号,用以连接到系统总线上。 CPU 对 I/O 端口的访问,就通过这些信号线来传递。 INT 信号则是由中断控制器发出的中断请求信号, 连接到 CPU 的可屏蔽中断请求信号上。 实际上 CPU 在收到中断请求后,还会发出一个中断响应信号, 这个信号会被连接到中断控制器的 INTA 引脚上。 后来在 8259 的基础上,又有了一些升级和功能的扩充, 这就是后来的高级可编程中断控制器,简称为 APIC。 那么再来看一看中断控制器在系统当中的连接情况。
IBM PC/XT 当中,就使用了一片 8259。 8259 和 CPU 之间,有中断请求信号和中断响应信号。 而另一端,8259 则连接了来自各个外设的中断请求信号, 比如有来自定时器,有键盘,有串行接口, 有硬盘,有软盘,还有打印机。 那 2 号中断请求信号是被保留的,并没有连接外设。 等一会儿我们还会提到,这根保留的中断请求信号用来做什么了。
那这是三十多年前最早的个人计算机了, 在现在的个人计算机当中,有些设备都已经没有了,比如软盘。 但是现代的计算机系统仍然要遵守最早的个人计算机当中确定的一些规则。 那么就以键盘所对应的这个中断为例,来看一看现在最新的个人计算机当中的情况。
如果我们在一台装了 Windows 操作系统的个人计算机上, 调出设备管理器,然后在设备管理器中找到键盘, 用右键看这个键盘的属性,我们就会发现, 它的中断请求信号仍然是连接到中断控制器的 1 号接口, 仍然遵守三十多年前,IBM PC 所制定的规范。
那在刚才的那个例子中我们可以看到, 一个中断控制器最多可以连接七个外设, 那如果有更多的外设应该怎么办呢?我们可以在系统中再增加一个中断控制器, 那这个中断控制器的请求信号不是连接到 CPU 的, 而是连接到原有的中断控制器的 2 号中断请求信号上。 那在这个中断控制器上,又可以再连接更多的外设。 这样两级中断控制器的结构,在早期的个人计算机当中使用了很长时间。
不过现在情况有一点不太一样, 因为很多 I/O 接口都集成到了南桥当中,所以这些 I/O 接口的中断请求信号实际上都是在南桥内部了。 因此在南桥内,一般也会实现一个中断控制器, 当然现在是 APIC 这样的中断控制器了。 这个中断控制器负责接收所有 I/O 接口的中断请求信号, 包括南桥内部集成的,和在外部独立的 I/O 接口。 它会将中断请求信号再送到 CPU 中去。
而现在的计算机当中,往往有多个 CPU, 其实每个 CPU 当中,都还会带一个中断控制器。 因为现在的 CPU 不但要接受中断请求信号,它也会发出中断, 现在 CPU 发出的中断,是用来跟别的 CPU 进行交互的。 比如两个 CPU 要进行一些协同的工作,那其中一个 CPU 在处理完了内存当中的一部分数据, 它就可以通过发出中断请求来通知另一个 CPU 进行后续的工作。 因此在现代的个人计算机当中,可能已经找不到一个独立的中断控制器的芯片了, 但其实中断控制器的功能已经变得更为丰富,数量也变得更多了。
我们还是回到简单的情况,来看看这些来自外设的,可屏蔽中断的处理过程。 当外设有中断的需求, 那它就会通过中断控制器向 CPU 发出中断请求信号, 而 CPU 则会中断当前正在执行的程序, 向中断控制器发出中断响应信号, 然后中断控制器再会通过其他的信号线, 将对应外设的中断类型码发给 CPU。而这个类型码, 其实也是在系统初始化时,通过写入中断控制器的 I/O 端口而设置的。 CPU 在得到了中断类型码之后,后续的处理过程就和内部中断是一样的了。
我们快速地浏览一遍。CPU 将相关的寄存器压栈, 然后清除 IF 和 TF 标志位,再取得对应的中断向量, 然后程序就会跳转到中断服务程序,在中断服务程序当中, 可以在适当的时机通过设置 IF 标志位,开放中断。 一旦开放了中断,就意味着在执行这个中断服务程序的过程中,CPU 还可能会响应其他外设发来的中断。 那在中断服务程序执行完之后,就会执行中断返回指令, 将返回地址等信息从堆栈中弹出,然后就可以回到刚才被中断的位置继续执行了。
如果在这个中断服务程序当中,开放中断后,外设又发来中断, 那其实 CPU 是会中断这个中断服务程序的执行,转而去响应这个新发生的中断。 这种情况就被称为“中断嵌套”。 当然,要想发生中断嵌套的情况, 必须要有比当前正在处理的中断优先级更高的中断请求, 那这时 CPU 就会去响应这个优先级更高的中断请求, 在执行完这个新的中断服务程序之后,再返回到刚才的中断服务程序当中继续执行。
我们再根据图示来看一看这个过程。 假设在一段主程序当中,开始是关闭了中断响应的。然后在这里打开了中断响应(STI), 之后就发生了中断,于是 CPU 就会转向中断服务程序。 那么在这里 CPU 的硬件已经自动设置了 IF 标志位为 0,屏蔽了外部的中断请求, 如果在这个中断服务程序的某个地方又打开了中断响应(STI), 而且在这个过程中又有外设发起了更高优先级的中断请求, 那 CPU 又会去处理这个新的中断。
我们要注意,对于 CPU 来说,现在在执行的这个中断服务程序也就像是一个普通的程序, 所以在这个过程中如果发生了中断请求, 它依然会进行同样的那些处理步骤,比如压栈、关中断、保存现场等等, 然后根据取回的中断向量进入到第二层的中断服务程序。 那如果在这个中断服务程序当中没有开中断, 或者开了中断但是没有更高优先级的外设发起中断请求, 那一直执行到中断返回指令,CPU 就会回到 刚才发生中断的地方继续执行,也就是第一层的中断服务程序。 如果之后没有再遇到中断请求, 就会一直执行到第一层中断服务程序的中断返回指令, 再返回到刚才主程序中断的地方,这就是一个简单的两层中断嵌套的过程。
现在我们已经知道了内部中断和外部中断是如何协同工作的, 而在现代的计算机系统当中大量地使用了中断来控制输入输出设备, 至于如何进行中断优先级的调配,什么时候能屏蔽中断,什么时候不能屏蔽中断, 这一些问题都是值得深入研究的。