什么是中断?
一个程序只会做自己的事情,当需要输入或者输出时就要用到外部设备,而外部设备相比于处理器要慢得多。在等待的时候,处理器只能不停的观察外部设备的状态变化。
为了能够更加高效的运用硬件资源,处理器应该能够为多用户多任务提供一级硬件支持。在单系统的系统中,允许同事由多个程序在内存中等待被处理器执行。例如当一个程序等待输入输出时,允许另一个程序从处理器那里得到执行权。
当一个程序正在执行的时候,它不会知道是否还有别的程序正在等待执行。所以,中断(Interrupt)工作机制就应运而生了。
中断的目的是打断处理器当前的执行流程,去执行其他与当前工作不相关的指令,执行完毕之后,要在返回原来的程序流继续执行。
这里就会有疑问:
1.中断是怎么发生的?
2.处理器是怎么处理中断的?
3.我们利用中断能做些什么?
外部硬件中断
外部硬件中断,就是从处理器外面来的中断信号。当外部设备发生错误,或者由更重要的事情发生,会让处理器临时来处理这些事情。
如图1所示(简化的示意图),外部硬件中断是通过两个信号线引入处理器内部的。这两个根线的名字叫NMI和INTR
两根信号线都是处理中断的,那为什么不能只有一根呢?这样似乎更方便,而且更有效率。但并不是所有的中断都是必须要及时处理的,这里就把中断分为两种,一种是非屏蔽中断、可屏蔽中断。
非屏蔽中断
有一些中断关乎整个系统的安全性,所以在任何时候都必须要及时处理。比如,当电池电量很低时,不间断电源系统会发出一个中断,通知处理器快掉电了。再比如,内存访问电路发现一个校验错误,这意味着内存访问的数据是错误的,处理器不应再继续访问。在这些情况下,处理器必须对这些中断采取措施,否则会对用户造成很大的损失。
所有的严重事件都必须无条件的加以处理,这种类型的中断是不会被阻断和屏蔽的,称为非屏蔽中断(Non Maskable Interrupt,NMI)。
中断源,中断信号的来源,或者说是产生中断的设备。如图1所示,在传统的兼容模式下,NMI的中断源通过一个与非门连接到处理器。处理器的NMI引脚是高电平有效的,而中断信号是低电平有效的。当不存在中断时,与非门的多有输入都为高,因此处理器的NMI引脚为低电平,这意味着没有中断发生。当有任意一个中断发生时,与非门的输入变为0,输出为1(高电平),处理器开始处理中断。
到这里,我们知道了处理器是如何知道由中断发生了,但是仅仅是知道由中断发生是不够的,处理器还需要知道具体是哪一个中断,以便于采取适当的处理措施。每种类型的中断都被统一编号,这称为中断类型号、中断向量或者中断号。 因为不可中断对处理器来说都是致命的,没有必要知道具体是那些事件。在实模式下,NMI被赋予了统一的中断号 2,不在进行细分。 一旦发生了二号中断,处理器和软件系统通常会停止继续当前工作,也不会纠正已经发生的问题和错误,最多由软件系统给出一个提示信息。
可屏蔽中断
更多的情况下,中断信号通常不是致命的。这类中断有两个特点,第一是数量很多,因为有很多的外部设备;第二是它们可以被屏蔽,被屏蔽后处理器将不会对它们进行处理。 这类的中断被称为可屏蔽中断。是否要屏蔽可屏蔽中断是用户自己决定的,这是处理器赋予用户的权利。
可屏蔽中断是通过INTR引脚进入处理器内部的。INTR引脚只有一个,不可能为所有中断提供引脚,而且处理器一次也只能处理一个中断,所以这里就需要一个代理,这个代理用来接受外部发来的中断信号。多个设备可能同时发出中断信号,所以这个代理也需要对这些中断信号进程仲裁,来决定哪一个中断先被处理器处理。
在个人计算机中,使用最多的中断代理是8259芯片,它就是通常所说的终端控制器。8259芯片提供15个中断信号,但中断号不固定。中断控制器芯片有自己的端口号,用户可以通过端口号来设置各引脚的中断号。因为该芯片是可以编程的,所以该芯片又叫做可编程中断控制器(Programmable Interrupt Controller,PIC)。图2为8259芯片。
这里为什么需要两个8259芯片呢?因为每一个芯片只有8个中断输入引脚。代理输出INT直接与处理器的INTR相连的称为主片(Master),第二片称为从片(Slave),这两个芯片形成级联关系。
之前说过8259芯片接受的是可屏蔽中断,所以在这里要如何屏蔽中断呢?在8259芯片中有一个 中断屏蔽寄存器(Interrupt Mask Register,IMR)。这是一个八位的寄存器分别对应这芯片中的8个中断输入引脚,对应的位是0还是1,决定了该引脚发来的中断是否能够达到处理器(0表示允许,1表示阻止)。8259芯片是可以编程的,主片的端口号是0x20和0x21,从片的端口号是0xa0和0xa1,可以通过这些端口对8259芯片进行编程。
中断能否到处理器,还要有处理器的来决定。 处理器中的标志寄存器中的IF位,叫做中断标志(Interrupt Flag)。IF为0时,所有从处理器INTR引脚来的中断信号都会被忽略掉;当IF为1时,处理器可以接受和响应中断。
实模式下的中断向量表
通过上面的内容,知道了中断是如何产生的,并且知道了中岛如何被处理器响应。
这里要说的是:什么是中断。
有那么多中断,我们又是如何找到中断的。
所谓的中断处理,就是让处理器去执行一段与该中断有关的程序(指令)。 处理器可以识别256个中断,理论上是要由256个与之对应的程序。这些程序的位置可以是随机的不是那么重要,处理器只需要知道这些程序的入口点,也就是中断处理程序的地址。处理器将它们的入口点存放在内存中从物理地址0x00000~0x003ff。共1KB的空间中,这个空间就是中断向量表(Interrupt Vector Table)。
我们知道在实模式下,如果我们想要知道一个段的地址,就需要知道段地址和偏移地址。所以在每一个中断在向量表中占用2个字,分别是中断处理程序的偏移地址和段地址。 中断0的入口点位于物理内存0x00000处,也就是逻辑地址0x0000:0x0000;中断1的入口点位于物理内存0x00004处,也就是逻辑地址0x0000:0x0004处;其他的入口点以此类推,但总是按顺序的。
中断的入口点存储在中断向量表中,先找到入口点在中断向量表中的位置,在通过存储的入口点找到中断处理程序。
当中断发生时,如果外部硬件到处理器之间的道路时通畅的,处理器在执行完当前的指令后,会立即着手为硬件服务。首先响应中断,告诉8259芯片准备着手处理该中断。然后,要求8259芯片把中断号发送到处理器。
在8259芯片中,每一个引脚都赋予一个中断号,之前说过8259芯片是可编程的,也就是引脚的中断号是可以改变的。但是这种编程也是有限制的,对8259芯片编程是以芯片为单位的。比如制定主片的中断号从0x08开始,那么IR0~IR7对应的中断号分别是0x08~0x0e。
中断信号来自那个引脚,这是8259芯片的工作,8259芯片会将对应的中断号告诉处理器,处理器拿到中断号后,会依次做下面的几件事情。
- 保护断点的现场。 首先将标志寄存器FLAGS压栈,然后清除IF位和TF位。接着将当前的寄存器CS和寄存器IP压栈。
- 执行中断处理程序。 此时处理器已经拿到了中断号,处理器将该中断号乘以4(因为每个中断在中断向量表中占4字节),这样处理器知道了该中断号在中断向量表中的偏移地址。然后处理器依次取出中断程序的偏移地址和段地址,并分别传送到IP和CS中。随后,处理器就开始执行中断处理程序了。
因为这里将IF标志位清除了,多以处理器将不会在响应硬件中断。如果想要更高优先级的中断嵌套,可在编写中断处理程序的时候,适时的使用sti指令开放中断。
- 返回到断点继续执行。 所有的中断处理程序的最后都应该是iret指令。执行这条指令,处理器一次从栈中弹出IP、CS、FLAGS的原始内容。
NMI发生时,处理器不会是从外部获得中断号,会自动生成中断号2,其他的处理过程和可屏蔽中断相同。
中断是可以随时发生的,但中断向量表的建立是由BIOS在计算机启动时完成的。BIOS为每个中断号填写入口地址,因为BIOS不知道大多数的中断处理程序的地址,所以一律将它们指向一个相同的入口地址,在那里只有一条指令:iret。也就是说,这些中断发生时,会立即返回。计算机启动后,操作系统和用户程序在根据自己的需要,修改表中的某些中断的入口地址,使它指向自己的代码。
内部中断
内部中断发生在处理器内存,是由执行的指令引起的。内部中断不会影响标志寄存器IF位,也不影响中断识别总线周期,它们的中断类型是固定的,可以立即转入相应的处理过程。下面举一些内部中断的例子。
处理器检测到div或者idiv指令的除数为零时,或者除法的结果溢出时,将产生中断0(0号中断),这就是除法错中断。再比如,当处理器遇到非法指令,将产生中断6。
软中断
软中断是由int指令引起的中断处理。这类中断也不会影响中断识别总线周期,中断号在指令中给出。
int 指令的格式:
int3
int imm8
into
int3是断点指令,机器指令码为CC。这条指令在调试程序是很有用。当处理器执行到int3时,即发生3号中断,专区执行相应的中断处理程序。
int n,后面给出的是中断号,int 3 的机器码为CD 03。比如:
int 0x00 ;引发0号中断
int 0x15 ;引发0x15号中断
into是溢出中断指令,机器码为0xCE。当处理器执行到这条指令时,如果标志寄存器的OF位是1,将产生4号中断。否则这条指令什么也不做。
BIOS中断
我们能否利用中断呢?
可以为多有的中断类型自定义中断处理过程。处理器允许256种中断类型,而且大部分都没有被硬件和处理器内部中断占用。这样我们就可以编写自己的中断。
编写自己的中断是很有好处的,因为在我们需要执行对应的中断处理程序时,不需要知道目标位置的段地址和偏移地址。
因为有了软中断,每次操作系统加载完自己之后,以中断处理程序的形式提供硬件读写功能,并把该历程的地址填写到中断向量表中。这样无论在什么时候,在我们需要该功能的时候,直接发出一个软中断即可,不需要知道具体的地址。
BIOS中断,又称为BIOS功能调用,为了方便的使用最基本的硬件访问功能。不同的硬件使用不同的中断号,比如,使用键盘服务时,中断号是0x16,及
int 0x16
为了区分针对统一硬件的不同功能,使用寄存器AH来指定具体的功能编号。比如,从键盘读取一个按键:
mov ah,0x00 ;从键盘读字符
int 0x16 ;键盘服务。返回时字符代码在寄存器AL中
BIOS可能会为一些简单的外围设备提供初始化代码和功能调用代码,并填写中断向量表,但也有一些BIOS中断是由外部设备接口自己建立的。