"中断"在计算机技术中占有极其重要的地位。它来源于我们日常的生活。举一个生活中的简单例子:A君正在自己的书房内读书,厨房的煤气灶上正烧着开水,客厅中有一部电话,A君的一位朋友约好要给他打电话。
读书是A君的主要工作,但A君同时又要处理其它事情:水开了,A君必须去关煤气灶;电话铃响,又需要去接电话。
对于这样的事情,A君可以有这样两种处理方案:
先读几行书,记下页码后去客厅看看有无电话打来,若无电话则再跑到厨房看看水开了没有,如果水未开就返回书房看书。如此循环。
"咳!我活得可真够累的啊......"A君感慨地说。
感慨归感慨,但是必须承认,这的确是个方案。而且只要循环的“频率”足够高,则这个方案还是行之有效的。
当然,人是不会这样工作找累受的,但CPU却能这样运行。比如某时刻CPU正在执行一个程序,打印机正在打印,打印字符要由CPU负责供给。而操作人员正要接键输入字符,输入的字符也要CPU负责保存。
在这样的情况下,CPU就可以按方案(1)处理这两项工作:执行一段程序,停下来转去执行一小段程序来判断是否有键盘输入,有输入则保存输入的字符;而后转去执行另一小段程序去给打印机发送数据;最后返回主要的程序继续执行,如此循环。
这样一种工作方式在一些结构非常简单的计算机中确实是有应用的,此种方式一般称为“设备轮循”。如果让CPU以很高的速度进行轮循就能保证每种外设都能及时得到CPU的支援。
当然,“设备轮循”使得CPU的运行效率很低,因为无论外设是否需要CPU提供服务CPU都要去“关照”它们。而如果某个外设需要CPU支援的时候CPU正在“关照”其他设备,那么此设备就可能因为得不到CPU的支援而陷入运转混乱状态。由此看来,这种令CPU无时无刻不在四处奔走、东挡西杀的工作方式是不能用在PC电脑中的。
A君专心致志地看书,若电话铃响,则记下正在看的页码,放下书去接电话。接完电话返回来继续从记下的页码处读书;若水开发出响声,则记下页码,放下书去关煤气灶,而后返 回来继续读书;若电话铃响同时水也开了,那么A君出于对生命的珍惜必然先去关煤气灶,然后去接电话;若在接电话的过程中水开了,那么A君必然会让对方先等一等,待他关闭煤气灶后,再返回来继续接电话,接完电话后再回到书房继续读书。
说了这许多,其含义不言而喻:我们是在收到“信号”后才中断正在做的事情,转去处理其它事情。而且“重要”的事情总是优先处理。
CPU的工作情况和我们人类相似,在我们使用的电脑中,CPU是以另一种工作方式--“中断方式”工作的。CPU专心执行一个主要的程序,对外部设备不闻不问。当键盘有键被接下时,键盘会通过电路向CPU发一个信号,CPU收到信号后就会记下当前程序将要执行的指令的地址,而后去执行一小段“中断服务程序”来保存键盘输入的数据。处理完毕,CPU又会回到刚才被中断的地方继续完成其“本职工作”;当打印机需要数据时,它也会向CPU发出信号,CPU收到信号后,同样会中断正在干的活,转去执行另一小段服务程序给打印机提供支援。
不难看出,以这种方式工作时CPU的效率就比较高。当外设没有要求CPU提供服务时,CPU会全力执行其主要的任务。而且只要设计出良好的中断信号传输与处理电路,这种工作方式的可靠性是很高的。所谓“中断”,指得就是CPU的这样一种工作方式。
这里有两个名词需要首先说明:
(1)中断源──中断的来源(产生中断信号的设备或指令);
(2)中断服务程序──CPU在收到中断信号后所执行的为中断源提供服务的小段程序。
依据中断源的不同,一般把中断分成三类:硬件中断、软件中断和处理机中断。硬件设备产生的中断就是硬件中断;由INT指令产生的中断称为软件中断;而由CPU本身产生的中断就是处理机中断。
无论是对硬件、软件还是处理机中断,CPU的处理方式都是一样的:
(1)当CPU收到中断信号或执行INT指令后,CPU首先会把标志寄存器推入堆栈,而后再把指令指针IP和代码段地址寄存器CS推入堆栈。这样做的目的是为了在执行完中断服务程序后能正确地返回被中断处继续执行程序,相当于我们记下正在读的页码。
(2)保存完返回地址后,CPU就转去执行相应的中断服务程序。
这里就有这样一个问题,CPU如何知道中断服务程序在内存中的位置呢?
如果PC电脑只有一个中断及其服务程序那么这个问题就简单了。不过我们使用的PC电脑所接的外部设备不止一个,键盘、软盘驱动器、打印机等等。每种设备基本上都用中断方式与CPU联系。况且中断不一定都来自外设,还可能来自处理机或软件。这就要求CPU可以区分不同的中断源,处理多个中断。
在设计8086/88CPU时,Intel公司的技术人员为其安排了256个中断,而且这些中断都被编了号,从0到0FFH。不同的中断源产生中断的号码不一样,例如处理机产生中断的号码范围固定为00H-07H;而外设产生中断的号码范围是08H-0FH,70H-77H两部分;“INT nn”指令中 “nn”可以是00H-0FFH中任一个数。
每个被使用中断都有对应的中断服务程序,所有这些中断服务程序在内存中的起始(入口)逻辑地址被组织在一起,保存于内存中的一段特定的区域内。这段区域从逻辑地址0000:0000开始,到0000:03FFH为止共1KB字节。
每4个字节保存一个中断服务程序的入口,从0000:00000开始,0-3字节保存0号中断服务程序的入口地址,4-7字节保存1号中断服务程序的入口地址,以此类推。保存所有256个中断服务程序的入口地址恰好使用1KB存储器。
由于每个入口地址都指向内存中的一段程序,所以我们引用数学中向量的概念,把这些入口地址称为"中断向量",256个"中断向量"组织在一起形成一个"表",我们把这个"表"称为"中断向量表"。这个表就是CPU取得中断服务程序入口地址的依据。给定一个中断号,即可根据下式计算其对应服务程序入口地址在中断向量表中的偏移量:
偏移量=nn×4
利用DEBUG程序可以观察到中断向量表。进入DEBUG,在"-"后打入"D0:0[Enter]",屏幕上就会显示出中断向量表中的部分入口地址。
为了能观察内存中任意位置存放的数据,DEBUG提供了一条"内存转储"命令,命令码是"D"(Dump)。使用方法很简单,在提示符后输入"D 逻辑地址 长度"并回车,DEBUG会从指定地址开始将内存中的数据以16进制形式显示在屏幕上,同时在屏幕右则显示出一些数据所对应的ASCII字符。
如果没有给出完整的逻辑地址,只给出偏移量,则DEBUG会默认DS为段地址。如果未指定地址,则DEBUG就认为起始地址是跟在由前一个D命令所显示的单元的后面。连续输入不带参数的D命令,可连续显示内存中的数据。下面列出了D命令的一些用法:
-D0:0[Enter] 从内存0:0处开始显示数据
-DES:100[Enter] 从内存中ES所指定段的偏移100H处开始显示数据
-D0B30:0 3FF[Enter] 从内存中0B30:0处开始连续显示1KB数据
下面列出的数据就是笔者所用PC中的一部分中断向量表。
-d0:0 5F16进制形式的数据 相应的ASCII码 0000:0000 89 3E 7E BE B0 05 70 00-C3 E2 00 F0 B0 05 70 00 .>~...p.......p. 0000:0010 B0 05 70 00 54 FF 00 F0-4C E1 00 F0 6F EF 00 F0 ..p.T...L...o... 0000:0020 76 18 B0 D3 88 20 B0 D3-6F EF 00 F0 6F EF 00 F0 v.... ..o...o... 0000:0030 6F EF 00 F0 6F EF 00 F0-57 EF 00 F0 B0 05 70 00 o...o...W.....p. 0000:0040 C4 21 B0 D3 4D F8 00 F0-41 F8 00 F0 EA 02 A7 B0 .!..M...A....... 0000:0050 39 E7 00 F0 AE 03 71 02-01 06 AC B2 D2 EF 00 F09 .....q.........
屏幕右边所显示的是和数据对应的ASCII字符,关于ASCII码的知识第一章中有介绍。在中断向量表中有一些向量为0,表示这个向量未使用。
① INT指令是“万能”的,它可以调用系统中任意一个中断,不论这个中断为谁服务。
有了中断向量表,CPU就可以根据中断号找到对应服务程序的入口地址。把这个地址装入CS和IP,CPU就开始执行中断服务程序。
每个中断服务程序的末尾都有一条"中断返回"指令,这个指令在形式上和RET相似:
助记符:IRET(Interrupt Return)
用 途:从中断服务程序返回被中断程序
格 式:IRET
执 行:CPU返回被中断处继续执行指令
指令看似简单,但CPU的实际动作却是很复杂的。在产生中断的时候CPU已经自动地把将要执行的指令的逻辑地址推入堆栈,那么执行IRET时CPU会自动地从堆栈中取出事先存放在堆栈中的返回地址并将其装人CS:IP中,同时CPU还会从堆栈中恢复标志寄存器的原状态,这样一个中断就处理完了。