中断就是打断处理器当前的执行流程,去执行另外一些和当前工作不相干的指令,执行完以后,还可以返回到原来的程序流程继续执行。在现实生活中我们也会经常遇到类似中断这样的事情,例如你正在用手机听歌,突然你的电话响了,这时处理器必须中断歌曲的播放,来处理这件更为重要的事件。
中断可以分为两大类:CPU内部中断和CPU外部中断。
1. 外部硬件中断
从字面意思理解,外部硬件中断就是处理器外面的中断信号,是由外设引起的(例如键盘)。当设备发生错误或者有数据要传送,又或者处理器交给它的事情处理完了(比如,打印已经完成)。它们都会拍一下处理器的肩膀,告诉它应当先把手头上的事情放一放,来临时处理一下。
外部硬件中断是通过两个信号线引入处理器内部的,从很早的时候,也就是8086处理器的时代,这两根线的名字叫做NMI和INTR,如下图所示:
2. 非屏蔽中断
NMI=Non Maskable Interrupt,中文译为非屏蔽中断,什么叫做非屏蔽中断呢?
举个栗子,例如内存访问电路发现内存里面的数据乱了,出错了,这种情况下CPU再努力工作也是没有意义的,在这些情况下,处理器必须对这种情况采取必要的措施,一旦置之不理,隐瞒真相必然会对用户造成不可挽回的损失。
所以,处理器的设计者通过两个引脚来明确区分不同性质的中断,这是很自然的事情,严重事件,必须无条件地加以处理,这种类型的中断是不会被阻断和屏蔽的,也就是:NMI。当产生一个中断时,就会通过中断引脚NMI通知处理器。
每种类型的中断都被统一编号,这称为中断类型号,或者中断号,或者中断向量,都是一回事。NMI非屏蔽中断一旦发生,对于处理器来说都是致命的,甚至是无法挽回,不可纠正的。在实模式下,NMI被赋予了统一的中断号2,一旦发生2号中断,处理器和软件系统通常会放弃正常工作的“念头”,也不会试图纠正已经发生的问题和错误。
3. 可屏蔽中断
相对于非屏蔽中断来说,可屏蔽中断(INTR)就没有那么“致命”了,可能会非常紧急,但不会那么恐怖,动不动就死机崩溃什么的。
可屏蔽中断有两个特点:
1.数量很多,毕竟有很多外部设备。
2.可以被屏蔽,也就是说处理器可以不处理中断。
可屏蔽中断通过INTR引脚进入处理器内部,处理器每次只能处理一个中断,在这种情况下,需要一个代理,来接受外部设备发出的中断信号。另外多个设备同时发出中断请求的几率也是很高的,所以该代理的任务还包括对这些中断进行仲裁,以决定让它们中的哪一个优先向处理器提出服务请求。
在个人计算机中,用得最多的中断代理就是8259芯片,Inter处理器允许256个中断,中断号的范围是0~255,8259负责提供其中的15个,但中断号并不固定,可以根据程序员设置,以防止发生冲突。
当然,我们也把上图中的这种芯片叫做:可编程中断控制器(Programmable Interrupt Controller. PIC)。
在以前的一些个人PC中就使用了两块8259芯片,并且把这两块芯片进行了“连接在一起”,一块芯片可以反馈8个中断号,两块芯片接在一起,就可以管理15个中断号。在当时,接在8259上的15个设备都是相当重要的,如键盘和鼠标、串行口、并行口、软磁盘驱动器、 IDE 硬盘等。这些中断引脚可以被其他设备使用,我们可以从上图看到,8259的主片引脚0(IR0)接的是系统定时器/计数器芯片,从片的引脚0(IR0)接的是实时时钟芯片(RTC)。
另外,在8259芯片内部还有一个中断屏蔽寄存器(Interrupt Mask Regidter. IMR),IMR是一个8位的寄存器,每个比特位对应着8个芯片的8个输入。
当外部设备通过某个引脚发来一个中断请求信号时,如果它没有被IMR阻断(0表示通过,1表示拦截),那么,这个中断信号就会被送到CPU内部。注意:8259芯片是可以编程的,主片的端口号是:0x20和0x21,从片的端口号是:0xa0和0xa1,可以通过这些端口访问8259芯片,设置它的工作方式,包括IMR的内容。
但中断信号即使进入了CPU内部,在CPU内部有一个标志寄存器,叫做IF,这就是中断标志位。(Interrupt Flag)。当IF=0的时候,所有从处理器INTR引脚进来的中断信号都会被忽略掉。当IF=1的时候,处理器可以接受和响应中断。
也就是说,当NMI(非屏蔽中断)发来中断信号时,CPU立即无条件(不执行中断响应周期,不受标志寄存器IF位的影响)地转入"2号中断处理程序"。当INTR(可屏蔽中断)发来中断信号时,若IF=0,CPU不响应中断请求。若IF=1,CPU响应中断请求。
修改IF标志位可以通过cli和sti这两条指令:
cli=IF=0
sti=IF=1
在计算机内部,中断发生得非常频繁,当一个中断正在处理时,其他中断也会陆续到来,甚至多个中断同时发生的情况,这都无法预料。但8259芯片会记住它们,并按照一定的策略决定先为谁服务。中断的优先级和引脚是相关的,主片的IR0引脚优先级最高,IR7引脚最低,从片也是如此,也就是说,当IR0和IR7都有一个中断发送给处理器时,那么IR0的中断会优先处理。
4. 中断处理
处理器收到中断信号以后,就要进行处理,当然有些中断信号是可以忽略的,8259主片和从片,合起来提供15种类型中断号。
8086处理器最多支持256个中断号,那么理论上就可以设置256段中断程序。在实模式下,处理器要求将它们的入口点集中存放到内存中从物理地址0x00000开始,到0x003FF结束,共1KB内存。
每一个中断处理程序必须有一个段地址和偏移地址才能进行准确物理地址定位,这个就是一个中断向量表。当中断发生后,处理器执行完当前的指令后,会立即着手为硬件服务。处理器首先会响应中断,告诉8259芯片准备着手处理该中断,接下来,CPU告诉8259芯片,把中断号送过来。
8259芯片中的每个引脚都对应着一个中断号。当然因为8259芯片是可以编程的,可以修改每个引脚对应的中断号,但不能任意修改。比如设置主片的中断号从0x08开始,那么引脚IR0~IR7所对应的中断号分别是:0x08 ~ 0x0E。
当有中断信号到来时,8259芯片会告诉CPU对应的中断号。处理器拿到这个中断号,会做以下几件事情:
1. 保护断点的现场。首先要将标志寄存器 FLAGS 压栈,然后清除它的 IF 位和 TF 位,TF是陷阱标志,这个以后再讲。接着,再将当前的代码段寄存器 CS 和指令指针寄存器 IP 压栈。
2. 执行中断处理程序。由于处理器已经拿到了中断号,它将该号码乘以 4(毕竟每个中断在中断向量表中占 4 字节),就得到了该中断入口点在中断向量表中的偏移地址。接着从表中依次取出中断程序的偏移地址和段地址,并分别传送到 IP 和 CS,然后处理器就开始执行中断处理程序了。
注意:由于IF标志被清除,IF=0,在中断处理过程中,处理器将不再响应硬件中断,如果希望这期间处理优先级高的中断嵌套,可以在编写中断处理程序时,适时用sti指令开放中断。
3. 返回到断点接着执行。所有中断处理程序的最后一条指令必须是中断返回指令 iret。这将导致处理器依次从栈中弹出(恢复) IP、 CS 和 FLAGS 的原始内容,于是转到主程序接着执行。
对于上面的几件事情,我们可以用伪代码来表示:
取得中断向量:
push f ;把flag标志寄存器压栈
TF=0,IF=0 ;清除IF位和TF位
push CS ;把CS,IP压栈,保护现场
push IP
IP=N*4 ;处理中断程序
jmp far 0x0000:IP
1.保存用到的寄存器
2.处理中断
3.恢复用到的寄存器
4.iret ;iret恢复现场
iret指令的功能可以它做了以下的事情:
pop IP
pop CS
popf
和可屏蔽中断不同, NMI发生时,处理器不会从外部获得中断号,它自动生成中断号码2,其他处理过程和可屏蔽中断相同。 中断随时可能发生,中断向量表的建立和初始化工作是由BIOS在计算机启动时负责完成的。 BIOS为每个中断号填写入口地址,因为它不知道多数中断处理程序的位置,所以,一律将它们指向一个相同的入口地址,在那里,只有一条指令:iret(Interrupt Return )。
也就是说,当这些中断发生时,只做一件事,那就是立即返回。当计算机启动后,操作系统和用户程序再根据自己的需要,来修改某些中断的入口地址,使它指向自己的代码。
5. 内部中断
和硬件中断不同,内部中断发生在处理器内部,是由执行的指令引发的,例如,处理器检测到div或者idiv指令的除数为零时,或者除法的结果溢出,将产生中断0(0号中断),这就是除法中断,当然处理器内部中断还有有:单步中断,断点中断等等...... 并且,内部中断不受标志寄存器IF位的影响。
再比如,当处理器遇到非法指令时,将产生中断 6。非法指令是指指令的操作码没有定义,或者指令超过了规定的长度。操作码没有定义通常意味着那不是一条指令,而是普通的数。内部中断不受标志寄存器 IF 位的影响,它们的中断类型是固定的,可以立即转入相应的处理过程。
6. 软中断
还有一种中断,叫做软中断,软中断是由程序员自己触发,使用int指令可以触发,int指令的格式如下:
int3
int imm8
into
int3是断点中断指令,机器指令为CC。这条指令在调试程序的时候很有用,当程序运行不正常时,多数时候希望在某个地方停下来(设置一个检查点),也称断点,这条指令就是用来做这件事的。需要注意的是int3和int 3是不同的指令,当然,最有名的软中断是BIOS中断,因为BIOS中断功能是在计算机加电之后,BIOS程序执行期间建立起来的,主要是为了访问基本硬件功能,关于BIOS中断大家可以参考书籍——《x86汇编语言:从实模式到保护模式》。