输入输出程序设计 输入输出的基本概念 无条件方式输入输出 查询方式输入输出 中断方式输入输出
输入输出的基本概念 从第2章的图2.1可以看到,计算机的硬件系统由CPU、内存、外设三大部分构成,它们相互之间通过一组信息传递的公共通道──总线联系在一起的。CPU和内存构成了计算机的主机部分,是计算机中的高速设备。而大多数外部设备都是慢速设备,用来把从主机以外采集到的数据送入主机内部,或者把主机内的数据传递到外部,外设中的外部存储器还可以存储大量的数据。那么,CPU作为计算机的核心,它又是如何控制外设的呢?
8.1.1 外设接口 外部设备的种类繁多,功能各不相同,控制的方法也各式各样。很多外设由于速度与信号的关系,无法直接连接在总线上与主机进行数据交换,需要在系统总线与外设之间设置一个“适配器”,又称为“接口”,用于把CPU来的控制命令转换成外设的控制信号,把外设的工作情况转换成CPU可以读取并处理的状态信号。 接口部件担负着总线信号与外设信号的转接工作。它一头与外设相连,能够从外设接收数据或向外设发出信号,另一头连接在系统总线上,能够直接接收来自总线的数据和控制信号,或者在适当的时候往总线发送数据。
CPU能够与内存进行数据交换,也可以与外设接口进行数据传递,两者工作的方式是非常相似的。从第2章的描述中我们已经知道,CPU如果要从内存读或向内存写一个数据,总是先在地址总线上发出地址信号,以选定操作对象,然后在控制总线上发出控制信号,通知操作对象完成什么样的操作,数据总线则提供被交换的数据进行传递的通道。CPU总是通过总线,以发送地址信号的方式选择操作对象,对内存对外设接口都是如此。所以,计算机系统中对内存进行了编号,就是内存的物理地址,对外设接口也进行了编号,这个号码称作“外设端口号”。每一个端口号对应外设接口中的一个存放字节型数据的元件,称为一个“外设端口”。一个外设接口中往往需要多个外设端口,占据多个外设端口号。CPU控制外设就是通过从这些外设端口中读取数据以及向它们发送数据实现的。
在计算机内外数据交换过程中,外设总是处于从属状态,它受来自总线信号的控制,按控制命令的要求完成相应的操作,并且可以从主机接收或向主机提供数据。所以大多数外设接口从功能上可以分为控制部件、状态部件和数据部件三大组成部分。控制部件又称为命令部件,专用于接收来自主机的操作命令,并转换成对外设的控制信号;状态部件负责向主机转达外设的当前工作情况;数据部件是内外数据交换的缓冲器,临时存放需要传递的数据。
通常,三大部件中的每个部件至少占据1个外设端口地址,每个端口都以字节为基本构成单位,因而1个外设接口一般最少占用3个端口地址。但是,有些外设能够接受的控制命令很少,只需要1字节中的1位或2位就够了,这时可以用1个控制端口的8个位分别控制不同的外设,把不同外设的接口集中在一起,共同占据一个外设端口号。另一方面,接口中的命令部件只用于接收控制命令,CPU对命令部件只写不读。状态部件刚好相反,只用于向CPU提供状态信息,CPU对它只读不写。因而有些接口把命令部件与状态部件设计为共同占用1个外设端口号,由总线上的“读”或“写”信号区分究竟哪个部件是当前的数据传递对象。
8.1.2 8088的独立编址方式 无论是内存还是外设端口,都是以字节为基本的数据单位。当总线上出现有效的地址信号时,每个字节型内存储器或外设端口都能够根据地址信号,判断自己是否被选中为数据传递的对象(完成这种判断的是地址译码器),没有被选中的自动不参与本次总线上的数据传递。从这个角度说,只要为内存和外设端口分别安排不同的地址,就可以从地址信号本身区分数据传递的对象是内存还是外设端口,从而把各个内存字节与外设端口编排一套地址号码,这种编排地址的方式称为“统一编址”或“混合编址”。
8088采用的是另一种编址方式。由于8088CPU在数据交换时除了能够发出地址信号外,还有一根特别的信号线(IO/M)可以发出两种不同的信号。当这根信号线上发出高电位信号时,表示当前总线上是外设操作;反之,这根信号线上是低电位时,表示当前总线上是内存操作。设计计算机系统时就可以利用这根信号线上的信号,区分操作对象是内存还是外设。这时,各个内存字节与外设端口可以分开编排两套号码,分别称为内存地址与外设地址。当CPU需要读写数据时,同时发出地址信号和 IO/M信号,这些信号一起送到地址译码器,地址信号选择了具体的地址号码,而 IO/M信号则选择内存还是外设。这种把内存和外设端口分开各自编址的方式称为“独立编址”。
8.1.3 控制外设的指令 由于8088采取了独立编址方式,就需要有特殊的指令控制对外设端口的操作。8088系统设计有两条专用指令:IN和OUT。 【指令格式】IN d1, d2 【功能】从d2指明的外设端口中读取1字节或2字节数据,送到操作数d1指出的地方。 【说明】 (1)8088系统限制d1只能是AL或AX。当外设端口号不超过255时,d2操作数可直接写端口号码, 是外设的直接寻址方式;端口号超过255时,必须先把端口号放在DX中,以DX作为d2操作数,这是外设的间接寻址方式。
(2)当d1是AL时,该指令从指定的端口中读1字节数据;当d1是AX时,CPU将从d2对应的端口读1字节数据到AL,从下一个端口号读1字节数据到AH,即总共读取16位数据送到AX 中。 【指令格式】OUT d1, d2 【功能】把操作数d2指明的1字节或2字节数据送到d1对应的外设端口中。 【说明】 (1)d2只能是AL或AX。当外设端口号不超过255时,d1操作数可直接写端口号码;端口号超过255时,必须先把端口号放在DX中,以DX作为d1操作数。
(2)当d2是AL时,该指令把AL中的8位数据送往d1指定的外设端口;当d2是AX时,该指令把AL中的8位数据送到d1对应的端口,把AH中的8位数据送往下一端口,即把16位数据送到d1对应的端口及下一端口中。 IN和OUT指令专门用于外设操作,必须与内存操作严格区分开。下面的例8.1用来说明IN、OUT指令与内存操作的MOV指令之间的差别,并请读者体会在数据传递期间IO/M信号的作用。
【例8.1】说明下面各指令或程序段的功能。 (1)MOV DX,61H IN AL,DX (2)MOV BX,61H MOV AL,[BX] (3)OUT 21H,AL (4)MOV SI,21H MOV [SI],AL
【解】 (1)先把立即数61H放到DX中,然后以DX中的61H作为外设端口号,从相应的外设端口读取1字节数据送到AL中。 (2)先把立即数61H放到BX中,然后以BX中的61H作为偏移地址,以缺省段寄存器DS 中的值为段地址,从相应的内存中取出1字节数据送到AL中。 (3)把AL中的1字节数据送往21H号外设端口。 (4)先把立即数21H放到SI中,然后以SI中的21H作为偏移地址,以缺省段寄存器DS 中的值为段地址,把AL中的1字节数据送到相应的内存中。
8.1.4 输入输出方式 外部设备是多种多样的,不同的设备需要不同的控制方法。CPU与外设之间进行数据传递时需要考虑外设的性能。对于多数慢速外设而言,如果CPU传送来的数据速度太快,外设来不及处理,就可能造成数据丢失;如果外设还没有准备好数据,CPU就已经发出了读操作命令,将读不到正确的数据。因此,计算机系统进行内外数据交换时,必须根据外设的特点采用适当的形式。总的来说,主机与外设之间数据交换的方法有4种:无条件方式、查询方式、中断方式、DMA方式。
1.无条件方式 又称为直接方式,是指CPU可在任何时刻直接以外设操作指令与外部设备进行数据传递。显然,这种方式对外设有很高的要求,它必须能像内存一样时刻准备着与 CPU进行数据传递,并且能够跟上CPU的速度,保证传送的信息的正确性。 2.查询方式 使用查询方式工作的外设必须至少有两个部件,其中之一是状态部件。CPU每一次与外设进行数据交换之前,先从状态部件读取信息,判断外设是否处于“就绪”(Ready)状态。如果来自外设的状态信息反映出外设“没有准备好”或正“忙”(Busy),说明还不能进行数据传递;反之,当CPU检测到外设已准备好(Ready)后,可以与外设进行一次数据交换。
3.中断方式 这是指每当外设准备好、能够进行数据传递时,就向CPU发出一个特殊的请求信号,称为中断请求信号。CPU收到中断请求后,暂停当前的工作,转而执行一段预先设计好的中断服务程序,完成对外设的数据交换。执行完中断服务程序后,CPU仍回到被暂停的程序继续执行。 4.直接存储器存取DMA(Direct Memory Access)方式 这是一种不通过CPU,在内存与外设之间直接进行高速数据交换的方法。通常,系统总线是在CPU的控制之下,CPU总是作为数据传递的一方,内存与外设其中之一作为另一方。当大量的数据需要传递时,在主机内部,数据不
可能完全放在CPU中,只能放在内存中。在CPU控制下进行大量数据的传递,就必须把内存中的数据读到CPU中,然后再写往外设,或者反方向,先把数据从外设读入CPU再写往内存。可见,数据必须以CPU作为过渡,才能到达它的目的地。DMA方式正是避免了这种过渡,让数据不经过CPU,直接从内存送到外设,或者反之。 进行DMA方式的数据传递必须有一个前提条件,就是CPU能够让出总线的控制权,交由DMA方式数据传递的专用控制器控制,当数据传递结束后,CPU再收回总线控制权。8088CPU支持这样的总线操作方式,因而8088系统可以进行DMA方式的数据传递。
DMA控制器专门用于控制内存与外设之间的直接数据传递,但是它没有数据处理能力。在一些计算机系统中还设计有带有处理能力的专用数据传送芯片,其工作方式与 DMA方式很相似,也需要CPU在适当的时候让出总线供其使用,这种数据传递方式称为“专用处理机方式”。 8088微型计算机不使用处理机方式进行数据传递,DMA方式需要涉及计算机硬件的内容过多,所以本书都不做详细介绍。
8.2 无条件方式输入输出 计算机系统中的扬声器是一种简单的输出设备,可以随时从CPU接收控制命令。图8.1是扬声器的连接原理图。 系统总线 8.2 无条件方式输入输出 计算机系统中的扬声器是一种简单的输出设备,可以随时从CPU接收控制命令。图8.1是扬声器的连接原理图。 系统总线 端口 61H: P7 P6 P5P4P3P2 P1 P0 1.193MHz 定时器 A B 与门 放大电路 扬声器 图8.1 扬声器与系统总线的连接
扬声器发声的基本原理是振动。扬声器口上的一层薄膜在电信号控制下往复振动产生声音,每秒钟振动的次数就是所发出的声音的频率,振动的幅度决定声音的强度。由于控制扬声器的信号是二进制的开关信号,不能对流过扬声器的电流的大小进行控制,因而不能控制扬声器薄膜的振幅,所以小扬声器只能发出固定强度的声音。但开关信号变化的频率是可控的,CPU正是通过控制开关信号的频率,让扬声器发出不同频率的声音。
由图8.1可以看到,控制扬声器的信号有两个来源,它们通过与门之后送往放大电路并控制扬声器发声。系统中的61H号端口是一个可读写的外设端口,能够存放一字节数据,其中的最低两位就用来控制送往小扬声器的两个信号源。最低位P0为0可以保证送往与门A输入端的信号为高电位,这时只需要交替变化次低位P1的值,控制与门的另一个输入端B端的信号即可。除此之外,还要控制P1 变化的频率,比如要让扬声器发出500 Hz的声音,就是要使P1位上的信号每秒钟变化500次,即每0.002秒是一个变化周期,因而要让P1位维持高电位(即置P1位为1)0.001秒,然后变成低电位,再维持0.001秒,如此交替变化。而发声时间则通过控制P1位交替变化的次数来掌握,比如让扬声器在1秒钟内保持发出500 Hz的声音,就要让P1位交替变化500次。
控制时间并不是件容易的事。如果要准确控制时间,可以参照微机原理中有关定时器的内容,采用后面说明的中断方式。在此只是为了说明直接数据传送的原理,故简化时间控制的方法,采用循环延迟的方式实现。需要说明的是,例8.2的程序虽然可以在各种档次的PC机上运行,但发出的声音频率有可能不一样。这是因为不同档次的机器执行循环延迟所花费的时间不同,而程序本身就是利用这个延迟来控制音频的。
【例8.2】利用无条件数据传送方式,让计算机的扬声器在1秒钟内保持发出500 Hz 的声音。 【解】完整的程序如下: delnum = 14000 sta SEGMENT STACK DW 1024 DUP(0) sta ENDS code SEGMENT ASSUME CS:code,SS:sta
delay PROC NEAR ;时间延迟子程序,以CX中的值作为 PUSH AX ;循环次数,做若干次的乘法产生时 PUSH DX ;间延迟的效果 del: IMUL AX ;用乘法指令延迟,执行时间较长 LOOP del POP DX POP AX RET delay ENDP main: MOV CX,500 sou: PUSH CX
IN AL,61H ;读出原61H端口的数据 AND AL,11111100B ;清最低两位 OUT 61H,AL ;送低电位到P1 MOV CX,delnum ;取控制延迟时间的循环次数值 CALL delay IN AL,61H OR AL,00000010B ;置P1位为1 OUT 61H,AL MOV CX,delnum
CALL delay POP CX LOOP sou MOV AX,4C00H INT 21H code ENDS END main 从这个例子可以看出,对扬声器的控制是不需要任何条件的,程序中在向61H号端口送数据时根本不考虑扬声器是否准备好、是否可接收数据等问题。由端口61H到放大电路构成了扬声器的接口电路,该接口以系统总线上送来的信号驱动薄膜的振动,按无条件方式进行数据传送。
8.3 查询方式输入输出 大多数外设不可能像扬声器那样工作,这些外设处理数据的速度和提供数据的速度往往比主机内部速度慢得多,因而CPU与这类设备进行数据传递前必须先判断它们是否“就绪”。CPU以查询方式从外设读取一批数据,以及向外设送出一批数据的流程如图8.2所示。
N N Y Y Y (a)查询方式输入操作流程 (b)查询方式输出操作流程 读状态端口 读状态端口 Ready ? Ready ? 从数据端口读一个数据 处理完? N Y 读状态端口 Ready ? 处理完? N Y 向数据端口写一个数据或向命令端口发一个操作命令 Y (a)查询方式输入操作流程 (b)查询方式输出操作流程
打印机是一种可以按查询方式工作的输出外设,它与主机连接的接口部件是打印机适配器,也就是平常所说的“标准并行接口”或“并口”。打印机接口中的三大部件齐备,它们各自占据一个外设端口号码,分别是: 数据部件 ── 378H; 状态部件 ── 379H; 控制部件 ── 37AH。 状态端口各位的含义如下: D7 D6 D5 D4 D3 D2 D1 D0 Busy ACK PE SELECT ERROR
(1)D7位:是打印机的“忙”信号。这一位为0表示打印机当前正处于“忙”状态,为1表示打印机不忙。
(1)D4位:允许中断信号。将该位置1,允许打印机以中断方式工作,为0则不允许。 控制端口各位的含义如下: D7 D6 D5 D4 D3 D2 D1 D0 INT SELECT INIT Auto Feed STB (1)D4位:允许中断信号。将该位置1,允许打印机以中断方式工作,为0则不允许。 (2)D3位:联机命令。置1将设置打印机的联机工作方式,控制打印机时总是把这一位置1,否则打印机不能正常工作。打印机正常打印时这一位需要保持1。
(3)D2位:初始化信号。正常工作时总是把这一位置1,需要把打印机重新初始化时,则在这一位先清0再置1,并且要维持清0的时间0 (4)D1位:自动走纸。置1要求打印机在打印完一行后(回车时)自动走纸,清0时则需要向打印机输出换行符(0AH)控制走纸。这一位通常被置为0。 (5)D0位:选通信号。CPU通过在这一位上先置1再清0,通知打印机从数据部件中取走一字节数据并打印。 把图8.2中查询输出的流程稍做修改,可以作为控制打印机以查询方式工作的程序流程,如图8.3所示。
计数器清0 读状态端口 N Ready ? Y 送数据到数据端口 送先高后低的STB信号 Y 打印完? N 计数器加1,取下一数据 结束 图8.3 查询方式控制打印机
【例8.3】控制打印机以查询方式工作,打印26个英文字母,共打印30行。 【解】 sta SEGMENT STACK DB 1024 DUP(0) sta ENDS data SEGMENT print DB 'abcdefghijklmnopqrstuvwxyz',0DH,0AH data ENDS code SEGMENT ASSUME CS:code,SS:sta,DS:data
init PROC NEAR ;打印机初始化程序 MOV DX,37AH MOV AL,08H OUT DX,AL MOV CX,1000 init1: LOOP init1 ;延迟,维持初始化信号一段时间 MOV AL,0CH OUT DX,AL ;结束初始化,保持联机 RET init ENDP
start: MOV AX,data MOV DS,AX MOV CX,30 ;30行 again: LEA BX,print ;待打印字符串首地址存放于BX中 next: MOV DX,379H wait: IN AL,DX TEST AL,80H JZ wait ;打印机忙,转去再读状态端口 DEC DX ;数据端口号 MOV AL,[BX] OUT DX,AL ;送数据到数据端口暂存 MOV DX,37AH MOV AL,0DH
OUT DX,AL ;置STB信号为1 DEC AL OUT DX,AL ;置STB信号为0 CMP BYTE PTR [BX],0AH JZ newline INC BX JMP next newline: LOOP again MOV AX,4C00H INT 21H code ENDS END start
8.4 中断方式输入输出 把例8.3的程序拿到计算机上去执行,可以发现从键盘上发出程序执行命令后,光标会停在命令的下一行闪烁,并且打印机开始工作,直到打印机打印完所有内容后才会出现系统提示符。也就是说,在打印期间,系统是不能做其它事情的。 分析一下例8.3的程序可以发现,它是让高速的CPU去适应低速的打印机,让CPU反复不停地查问打印机的状态,直到打印机准备好。由于两者的速度差很大,很可能会出现CPU查问成千上万次以后才能等到打印机准备好的信号,送出1个字节的数据。显然,这种数据传递方式的效率是极低的。
设想让CPU把反复查问的时间利用起来去做别的事情,而让打印机在准备好接收一个数据时就向CPU发出一个信号。当CPU接到这个信号时,暂时停止正在执行的工作,以很短的时间为打印机送出下一个数据,然后恢复原工作。这样,尽管打印的速度并没有提高,但把高性能的CPU解放出来,可以做其它的事情,从而提高了整个系统的工作效率。中断式数据传递正是基于这种思想。
8.4.1 中断的基本概念 1.中断 CPU暂停正在执行的程序,转去完成另一件工作,完成后再回到原来的程序继续执行的现象称为中断。 8.4.1 中断的基本概念 1.中断 CPU暂停正在执行的程序,转去完成另一件工作,完成后再回到原来的程序继续执行的现象称为中断。 2.中断源 能够导致CPU中断的事件称为中断源。如果引发中断的事件来自CPU的内部,这样的中断源称为内中断源,比如CPU执行了一个会产生溢出的除法操作,就会产生一个内部中断。如果是由CPU外部的信号引发中断,这种中断源称为外中断源,打印机申请中断就是一种外中断源。
3.中断源分类 按照引发中断的中断源的位置不同,8088系统把中断源分为内中断源和外中断源两大类。来自CPU内部的中断请求是内中断源,8088系统的内中断源有除法溢出、执行中断指令和单步中断(CPU每执行一条指令都产生一次的中断请求,主要用于程序的调试)。外中断源是来自CPU芯片外的中断请求信号,8088芯片有两个引脚可以接收外中断请求信号,分别是NMI和INTR,并且8088对这两个引脚上的中断请求信号的处理方式不太一样。根据中断信号来自哪一个引脚,外中断源又再分为两类:把中断请求信号通过NMI引脚送往CPU的中断源称为不可屏蔽外中断源,INTR上的则称为可屏蔽外中断源。 也可以按产生中断请求的方式不同,把中断源分为软件中断源和硬件中断源,由它们导致的中断又分别称为软中断和硬中断。除法溢出中断、单步中断是内中断源中的硬中断源,外中断源都是硬中断源。软中断则是由指令系统中的中断指令导致的中断。
4.中断号 在计算机系统中,各种中断源都被统一地编排了一个互不相同的号码,用以唯一地标识一个中断源,这个号码称为中断号。在8088系统中,中断号的有效范围是0到255。常用的中断号与中断源的对应关系见表8.1。 5.中断源识别 由于中断源有不同的类型,向CPU申请中断的方式也各不相同。当CPU知道有中断请求后,还必须判断出究竟是几号中断请求。CPU确定中断号的过程称为中断源识别。如果中断请求来自CPU内部,CPU内有相应机制可以取到内中断请求的中断号;如果是不可屏蔽外中断请求,系统只安排了唯一的中断号(2号);当中断请求来自CPU的INTR外引脚时,情况就比较复杂了。
6.中断优先级 由于8088共支持256个中断源,尤其是包括若干硬中断源,各种中断源就有同时提出中断请求的可能。当多个中断申请同时送到8088时,CPU必须能分别轻重缓急妥善处理。CPU分辨各中断源优先次序的方式是预先把所有中断源进行分级,称为中断优先级。当CPU遇到同时有两个或两个以上的中断申请时,就按它们的优先级次序,先为级别最高的中断源服务。
8088把所有中断源划分为4个等级,以0级为最高,依次降低等级。各中断源的等级划分情况是: 0级 ── 除单步中断以外的内中断源; 1级 ── 不可屏蔽外中断源; 2级 ── 可屏蔽外中断源; 3级 ── 单步中断。 不同级别中的两个中断源同时申请中断时,CPU可以根据级别高低决定服务的先后次序。但同级中的两个中断源同时申请又如何处理呢?在0级中断源中,所有中断源由CPU执行指令产生的,只有执行DIV或IDIV指令时才有可能产生0
号中断请求,执行一条中断指令只能产生一个中断号,而CPU在任何时刻只能执行一条指令,所以不可能同时有两个或两个以上的0级中断请求。1级与3级中断源分别各有一个,不涉及同时产生中断请求的问题,只有2级比较麻烦。 所有可屏蔽外中断源都处于中断优先级中的2级,这些外中断源都必须通过CPU的INTR引脚向CPU提出中断申请,如果它们当中出现同时申请的现象,将由中断控制器处理。在8088系统中,中断控制器可以把它管辖的所有可屏蔽外中断源再进行内部分级,当同时出现多个中断申请时,由中断控制器判别相互间优先级的高低,并把其中最高级别的可屏蔽中断请求通过INTR送达CPU。
7.中断屏蔽 如果某个中断源发出中断请求后,CPU置之不理,继续完成自己的工作,这种现象称为中断屏蔽。8088系统中对各种中断分类处理,0级和1级中断是不能被屏蔽的,当CPU收到0级或1级中断请求时,必须立刻放下正在执行的程序进行中断处理;但2 级或3级中断就有被屏蔽的可能。3级中断是否屏蔽由标志寄存器中的TF标志位决定,当TF=0时,CPU将不响应单步中断请求。指令系统中没有专门指令可以直接针对TF标志位操作,但可以通过PUSHF和POPF指令达到修改TF值的目的。2级中断是否屏蔽受两个方面的控制:一是标志寄存器的IF标志位,如果IF=0,所有的2级中断源都被屏蔽;另一个可控制2级中断屏蔽的是中断控制器,中断控制器是作为8088系统的一个外设,CPU可以通过命令的形式通知中断控制器屏蔽掉几号中断请求,这种方式可以只屏蔽2级中断源中的某几个而不是屏蔽所有2级中断。
8.中断服务程序 CPU响应中断就是暂停正在执行的程序,转而为中断源进行相应的服务,称为中断服务。中断服务当然是通过执行一段程序来实现的。CPU响应某个中断时去执行的程序称为“中断服务程序”或“中断处理程序”。显然,如果要系统正常工作,中断服务程序就必须长期保存在内存中,保证CPU随时可以执行它。CPU在两种情况下会转去执行中断服务程序,一是正在执行的程序中遇到了一条中断指令(INT指令),二是硬中断源产生了中断请求且没有被屏蔽。第二种情况是由硬件中断源引起的中断,也就是说,不需要CPU去执行什么专用指令,只要出现没被屏蔽的硬中断申请,就会导致CPU去执行相应的中断服务程序。
各个中断源都对应地有自己的中断服务程序,当机器启动完成后,这些中断服务程序是操作系统或ROM BIOS中的程序段。这些中断服务程序也是可以修改的,只要用户程序能够把一段程序长期保留在内存中(即常驻内存),并且通知CPU这就是某个中断的中断服务程序。
9.中断向量 既然每个中断服务程序都放在内存中,当然就有其入口地址,这样,当CPU响应中断时才能知道转到哪里去执行中断服务程序。入口地址是一个完整的逻辑地址,包括16位的段地址和16位的偏移地址,由总共32位数据构成,需要占据4个字节的存储空间。把中断服务程序的入口地址的各个字节按照一定的规则排列起来,构成的一个有特定含义的数据组合称为“中断向量”或“中断矢量”。8088系统规定其中断服务程序入口地址的4个字节的排列规则是: (偏移地址低字节,偏移地址高字节,段地址低字节,段地址高字节) 为了说明的方便,中断向量通常都用十六进制书写。比如某中断服务程序的入口地址在F000:EF05处,表示成中断向量就是: (05,EF,00,F0)
10.中断向量表 8088系统中共有256个中断源,每个中断源都有自己的中断向量,把所有这些中断向量集中起来,按照中断号由0到255的顺序,从内存物理地址为0处开始依次存放,构成一张“中断向量表”。每个计算机系统的中断向量表都有自己固定的位置和长度,8088系统是把这张表放在内存的最低端,共占用1024个字节。图8.4是中断向量表的示意图。
… 00000 0号中断服务程序偏移地址低字节 00001 0号中断服务程序偏移地址高字节 00002 0号中断服务程序段地址低字节 00000 0号中断服务程序偏移地址低字节 00001 0号中断服务程序偏移地址高字节 00002 0号中断服务程序段地址低字节 00003 0号中断服务程序段地址高字节 00004 1号中断服务程序偏移地址低字节 00005 1号中断服务程序偏移地址高字节 00006 1号中断服务程序段地址低字节 00007 1号中断服务程序段地址高字节 00008 2号中断服务程序偏移地址低字 … 003FF 0FFH号中断服务程序段地址高字节
11.中断嵌套 在CPU执行一个低级别的中断服务程序时,如果系统中又产生了一个高级别的中断请求,这时系统会暂停低级中断服务,优先处理高级别中断,处理完后再继续低级中断服务。这种高级别中断打断低级别中断服务的现象称为中断嵌套。 中断嵌套的典型例子是,在一个中断服务程序中,需要在屏幕上显示一些信息,屏幕显示需要用到后面将要说明的10H号中断调用,因此在中断服务程序中就会写有INT 10H指令。这是一条内中断调用指令,是最高级别的中断,在执行到该指令时,系统会转去先进行10H号中断服务,在屏幕上进行显示,10H号中断返回后,再继续原先低级别的中断服务。
8.4.2 中断处理过程 不论是软中断还是硬中断,当CPU响应中断时,自动完成下列操作: (1)取中断号n(中断源识别)。 (2)把标志寄存器(PSW)的内容入栈。 (3)当前CS的值入栈。 (4)当前IP的值入栈。 (5)把标志寄存器中的IF和TF标志位清0。 (6)从物理地址4×n处取出4个字节的n号中断的中断向量,设取出的数据依次是b0、b1、b2、b3,把(b1, b0)拼成一个字型数据送到IP中,(b3, b2)拼成一个字型数据送到CS中。
(7)按CS:IP的新值继续执行。 这些操作是由硬件自动完成的,是CPU响应中断这一过程中密不可分的几个步骤,不能把它们拆开来,理解作依次执行几条指令的结果。响应中断的过程中,有三个字型数据被入栈保护,其中包括CS和IP。CS与IP的专职就是存放下一条指令的逻辑地址,它们的值被入栈保护,就意味着将来可以从栈中取出保存的值,恢复被中断的程序继续执行,就像子程序调用与返回一样。 响应过程中的第(6)个操作是把CS和IP修改为n号中断向量的值,也就是把CS和IP改为n号中断服务程序的入口地址,继而转入中断服务程序执行。当中断服务完成后,在服务程序的最后有一条特别的指令IRET作为结束。该指令将把中断时入栈保存的三个字型数据分别恢复到原出处,使CPU从中断服务程序转回到中断时的程序继续执行。
8.4.3 与中断有关的指令 在中断的有关概念描述中多次提到软中断是CPU执行中断指令时产生的中断,除了中断指令外,中断服务程序结束时也需要执行一条特殊的指令,以告诉CPU中断服务到此为止。关于2级中断的屏蔽标志位IF还有两条专用指令,分别说明如下。 【指令格式】INT n 【功能】产生一次n号中断请求。由于这是中断指令,属于内中断,具有最高级别,CPU必然响应,因此该指令将导致一次n号中断处理过程。具体的操作见8.4.2节中的中断响应时硬件自动完成的动作。
【说明】 (1)指令格式中的n是一个立即数,用以代表中断号,有效范围是0到255。 (2)这是一条放在用户程序中的指令,与随时可能发生的硬件中断不同,这是程序员有意识地安排在程序中的一个中断,因而程序员很清楚这个中断指令执行的效果。这样使用的中断,其服务程序往往是系统预先编写好的一些专用子程序,完成一些特定的服务功能,供用户程序使用,就比如DOS提供的服务程序让用户程序以INT 21H指令去调用一样。这类程序段与子程序不同的是,它们在系统启动时就已进入内存中,不需要像子程序那样与调用它的程序相联接。因此,习惯上又把这类程序段称为中断服务子程序,当作子程序一样使用,只是调用方式不同而已。
(3)指令格式中的n只要求在0到255之间,没有其它的限制。也就是说,不论是哪一个中断源,包括应该由硬件引起的外中断源,只要知道它的中断号,就可以用一条INT指令产生一次中断调用。但对硬件中断对应的0号、8号到0FH号,以及70H到 77H号,用INT指令去调用可能产生一些意想不到的结果,也有可能造成死机。 【指令格式】IRET 【功能】从栈中弹出3个字,第1个弹出的送到IP,第2个到CS,第3个到PSW。
【说明】这是专门为中断服务程序设计的一条指令,通常是中断服务程序的最后一条指令,它的功能与中断响应时硬件自动完成的动作相对应,从而保证不论是硬中断还是软中断,在中断服务结束后,CPU都能回到正确的位置继续执行。 【指令格式】CLI 【功能】把标志寄存器的IF标志位清0,使CPU不响应可屏蔽外中断。 【指令格式】STI 【功能】把标志寄存器的IF标志位置1,允许CPU响应可屏蔽外中断。
CLI与STI指令总是配合使用,使得计算机系统在某一段时间内不响应任何可屏蔽外中断请求。CLI是屏蔽掉所有2级中断的简单方法,但在应用时请小心,如果用户程序执行过程中屏蔽了所有2级中断,将使得系统不能从键盘、鼠标等输入设备上接收数据,这将使操作人员失去对机器的控制。所以,总是在屏蔽2级中断一段时间以后再把它打开。在程序中屏蔽中断的一个很好的理由,是不想要自己的程序在需要连续执行时被硬件服务打断,这通常是用在程序中需要执行一些不能暂停的任务的情况下。
8.4.4 系统提供的中断服务子程序 当计算机启动成功之后,内存中已经存放了很多具有固定功能的子程序,操作系统DOS提供的以INT 21H的形式调用的子程序只是其中的一部分,还有一部分是BIOS中断服务子程序,以及已装载的程序所提供的功能。 DOS提供的中断服务子程序分成很多子功能,完成各个子功能的程序段都集中放在一起,并且有一个总控程序,构成了一个整体。整个中断服务子程序的入口地址放在了21H号中断向量中。这是一个软件中断,调用方式是INT指令,并规定调用时AH中必须放子功能号,不同的子功能还需要有不同的入口参数。前面章节中已经讲述了其中的1号、2号、9号、0AH号和4CH号子功能,实际上DOS提供给用户程序使用的子功能很多,包括如何读写文件、如何申请和释放内存、如何修改中断向量、如何取得及修改系统当前的日期和时间等等,不能一一列举。
BIOS是固化在计算机的内存ROM芯片中的程序,其中包括计算机启动时最初执行的一些程序,从设备自检、系统初始化,到引导操作系统。BIOS中还有很大一部分是提供了一些可供用户程序使用的中断服务子程序,这些子程序都用于对外部设备的直接控制,主要涉及键盘、显示器、打印机、串行通讯等,由于这些设备的控制方法比较复杂,如果让应用程序直接用输入输出命令去控制,就会给程序的编制带来巨大的障碍。而这些设备的控制程序虽然较长,但都有一个固定的模式,适合于做成公共子程序的形式供各个程序调用,BIOS中就是存放着这样的子程序。各个主要的输入输出设备都有相应的子程序,这些子程序分在了各个中断服务程序当中,包括10H、14H、16H、17H号等几个中断。
8.4.5 中断与子程序的比较 子程序是程序设计的一种常用方法,一般是把具有固定功能、在程序中无规律重复使用的程序段做成子程序,在需要的地方调用;中断是计算机系统支持的一种重要功能,当发生中断时,系统执行一段特定的程序。根据中断源的不同,需要把中断分为软件中断与硬件中断分别看待,软件中断、硬件中断与子程序之间有一些共同之处: (1)都需要相应程序段的支持。发生子程序调用时,系统转去执行一段子程序,并在执行完后返回调用处继续执行;发生中断时,系统也是转去执行一段中断服务程序,执行完后返回中断点继续执行。被调用的程序段一定要在内存中。
(2)软件中断与子程序都由特定指令调用。软件中断由指令INT n调用,子程序调用指令是“CALL 子程序名”。不论是中断还是子程序调用,都会使系统修改CS和IP从而实现转向。 (3)发生调用时,系统自动记载返回地址。不论是中断还是子程序调用,系统在转入子程序/中断服务程序之前,都会把返回地址(IP或者是CS和IP)入栈保存。调用完成后,正是根据栈中保存的值,才能返回到正确的位置。 (4)软件中断和子程序都可以带有入口参数和出口参数。由于软件中断和子程序都是具有固定功能的服务性程序段,都是按固定模式进行数据处理,通常,在调用前需要知道被处理的数据是什么或在什么地方,即入口参数,调用后又需要把数据处理的结果通知调用者,即出口参数。
(5)可以用子程序调用代替软件中断的调用指令。INT指令与FAR类型子程序调用的CALL指令之间的主要差别在于是否把标志寄存器入栈,因此,只要能够先把标志寄存器入栈保存,再用CALL指令同样可以进行软件中断服务子程序的调用。比如,下面的方法完全可以代替INT 21H指令: MOV BX,0 MOV DS,BX ;中断向量表的段地址 MOV BX,84H ;21H号中断向量在中断向量表中的偏移地址 PUSHF CALL FAR PTR [BX] ;以子程序调用的CALL指令去调用21H号中断服务子程序
特别的是,这种用法很不合常规,并且上述程序段中的最后一条指令在MASM 5 特别的是,这种用法很不合常规,并且上述程序段中的最后一条指令在MASM 5.0下会出现语法错误。解决这个问题的方法是,编程人员手工地把这条指令翻译成机器码(查阅有关资料,或者经调试软件DEBUG的处理可以知道,该指令的机器码由两字节组成,分别是0FFH和01FH),然后直接把机器码置入源程序中,即把上面的CALL指令用下面的一行代替: DB 0FFH,01FH
软件中断、硬件中断与子程序三者之间也存在着本质的差别,主要体现在: (1)调用方式不同。软件中断由INT指令调用,子程序用CALL指令调用,而硬件中断是由硬件提出申请,不需要任何指令。 (2)系统保护的值不同。中断调用时,系统会把标志寄存器、CS和IP入栈保存,而子程序调用时,系统只入栈保存IP或者CS和IP。 (3)返回方式不同。中断返回指令是IRET,而子程序返回指令是RET,并且子程序还有NEAR和FAR两种类型。
(4)共享方式不同。硬件中断的服务程序不能被其它程序共享,而是直接由系统掌握,软件中断的服务程序可以被任何程序以INT指令的形式调用,并且调用者不必关心中断服务程序到底在内存的哪一个地方,也不需要把中断服务程序与调用它的程序拼装到一起。子程序的共享方式比较丰富,但有一点,就是被调用的子程序必须与调者拼装在一起,形成最终的执行文件。 (5)在内存中存在的时间不同。中断服务程序通常是长期保留在内存中,而子程序是随可执行文件一起进入内存,当可执行文件执行完后,子程序所占用的内存也随之释放。
*8.4.6 编写中断服务程序 中断服务程序一般是长期保留在内存中的,在用户程序结束后还能够被其它应用程序调用,或者是CPU在响应硬件中断时调用。因此,编写一个中断服务子程序还需要掌握以下技术:如何让一段程序常驻内存,如何修改中断向量使其指向新的中断服务程序。 8.4.6.1 常驻内存技术 内存是由操作系统管理的,DOS专门为驻留程序设计了一个功能调用。 【驻留方法】在AH中放31H,在DX中放需要驻留的程序的节长度,然后以INT 21H指令调用DOS的结束并驻留子功能。
【说明】 (1)驻留前要告诉DOS,驻留程序的长度是多少。方法是把驻留长度放在DX 中,长度单位是“节”而不是字节,1节等于16个字节。如果需要驻留的程序长度是n 字节,则DX的值可通过下面的计算式算得: DX=(n ÷ 10H)+1+10H 其中(n ÷ 10H)+1是计算出驻留程序需要多少“节”,加1是为了预防驻留程序以字节计算的长度不是16的整数倍。再加16节是因为每个程序在调入内存时,操作系统都为它安排了一个称为“程序段前缀(PSP)”的专用内存区,并且放在程序的前面,这个程序段前缀的长度是256字节,刚好16节,它必须与需要驻留的程序一起驻留在内存。
如果一个应用程序中编写了一段程序需要常驻内存,总是把这段程序写在代码段的最前面,如果数据段也需要驻留,则应该数据段在前,代码段在后。计算驻留长度时,应该把数据段的长度加上代码段中驻留部分的长度一起计算。比如,一个应用程序由代码段、数据段、堆栈段构成,数据段的各个变量总共占据200个字节,代码段中需要驻留的部分有500个字节,则段的编排次序应该是数据段、代码段、堆栈段, 因为堆栈段是不需要驻留的,驻留节长度是:
数据段:200B÷16+1=13节 代码段:500B÷16+1=32节 PSP: 16节 ────────────── 总计: 66节 因此,调用DOS的31H号子功能进行程序驻留前,必须把DX置为66,即42H。
8.4.6.2 修改中断向量的技术 由于中断向量共有4个字节,8088指令系统中最多只能一次送16位的字型数据到内存,所以修改中断向量至少要用两条指令才能完成。在修改中断向量时还必须保证一点,就是不能允许在修改过程中(只修改了其中的1个字时)产生相应号的中断请求,因为这时中断向量还是一种不完整的状态。即使产生了相应的中断请求也必须能屏蔽掉,保证修改的连续性和中断向量的完整性。比较好的方法是利用 DOS提供的一个子功能进行修改。 【设置中断向量】 入口参数:AH=25H AL=中断号 DS:DX=新的中断服务程序的入口地址 调用:INT 21H
8.4.6.3 中断编程实例 8088系统中有一个定时器,它每隔约0.05秒向CPU发出一次8号中断请求,这是一个硬中断,不受程序的控制。8号中断服务程序中有一条调用1CH号中断的指令,而1CH号中断是一个可供用户使用的中断,它只由8号中断调用,与硬中断有类似的效果。下面就利用1CH号中断,编写一个时钟程序。 【例8.4】编写一个时钟程序,要求把时钟信号显示在屏幕的右上角,并在程序结束后常驻内存。
【分析】由于系统每隔0.05秒就产生一次1CH号中断,因此在1CH号中断服务程序中应该有一个0到20的计数器,初值为20。每次中断调用该服务程序时,就把计数器的值减1,当计数器的值没减到0时,说明还没有中断20次,即不足1秒钟,因而不需要更新钟的读数。当计数值到0后,就需要让时钟向前走1秒,把秒数加1,若满 60秒则向分钟数进1,分钟数满60后再向小时数进1,小时数满24就清0,并且要把已走了1秒的钟的当前读数显示在屏幕上。另外,为了计数下一秒,还要把0到20的计数器重新置初值20。处理完这些工作后,中断结束并返回。 主程序需要完成以下工作:把1CH号中断向量改为新的中断服务程序的入口地址,让新的中断服务程序常驻内存。
【解】 code SEGMENT ASSUME CS:code,DS:code clock PROC FAR DEC CS:[x] ;计数器减1 JZ c2 ;满1秒转 JMP cend ;不足1秒,直接转中断结束 c2: PUSH DS PUSH ES PUSH AX PUSH BX PUSH CX
PUSH DX PUSH SI PUSH DI ;保护现场 PUSH CS POP DS MOV [x],20 ;对0到20的计数器重新赋初值 INC [s] ;秒数加1 CMP [s],60 JB c1 ;不满60秒转 MOV [s],0 ;计满60秒后,秒数清0 INC [m] ;分钟数加1 CMP [m],60
JB c1 ;不满60分钟转 MOV [m],0 ;计满60分钟后,分钟数清0 INC [h] ;小时数加1 CMP [h],24 JB c1 ;不满24小时转 MOV [h],0 ;计满24小时后,小时数清0 c1: MOV AL,[s] MOV AH,0 MOV DL,10 DIV DL OR AX,3030H
MOV [t+6],AX ;把秒钟数拆分成两位ASCII送入变量t+6处 MOV AL,[m] MOV AH,0 DIV DL OR AX,3030H MOV [t+3],AX ;把分钟数拆分成两位ASCII送入变量t+3处 MOV AL,[h] MOV [t],AX ;把小时数拆分成两位ASCII送入变量t处
MOV CX,8 CLD MOV AX,0B800H MOV ES,AX ;取显示缓冲区段地址 MOV AH,1FH ;时钟读数的字符属性,兰底,高亮度白字 LEA SI,[t] MOV DI,71*2
c3: LODSB STOSW LOOP c3 ;以直写屏方式显示,参见下一章 POP DI POP SI POP DX POP CX POP BX POP AX POP ES POP DS
cend: IRET t DB '00:00:00' ;存放时钟读数的变量 h DB 0 ;计小时数的变量 m DB 0 ;计分钟数的变量 s DB 0 ;计秒钟数的变量 x DB 20 ;0到20次的计数变量 clock ENDP main: MOV AX,code MOV DS,AX MOV AH,2CH
INT 21H ;取当前系统时间 MOV [h],CH ;置小时数初值 MOV [m],CL ;置分钟数初值 MOV [s],DH ;置秒钟数初值 LEA DX,clock MOV AX,251CH INT 21H ;置1CH号中断向量,使指向CLOCK子程序 MOV AX,3100H LEA DX,main
MOV CL,4 SHR DX,CL ADD DX,11H ;计算驻留程序的节长度 INT 21H ;结束并驻留 RET code ENDS END main
本 章 要 点 输入输出设备是计算机对外发挥作用的途径。不同的外设有各自的特点,需要不同的控制方法,但总的来说,外设都需要通过一个接口部件与计算机的主机相连。接口负责主机内外的信号转达,接口通常包括命令部件、数据部件和状态部件三大组成部分,每个部件占用一个或多个端口地址。 输入输出的主要方式有4种。直接方式适用于那些能与主机速度相匹配的外设,查询和中断方式则用于速度较慢、控制较复杂的外设,而DMA方式提供了一种不通过CPU直接在主机与内存之间进行高速数据传递的方法。用户可以编写程序对以直接方式、查询方式、中断方式工作的外设进行操作控制。 中断是CPU的一种重要工作方式,由中断源发出中断请求信号开始,经过中断源识别、现场保护、执行中断服务程序、现场恢复的过程,使CPU在完成中断服务后能接着被中断的程序继续执行。
习 题 八 8.1 用图8.5的例子解释中断处理的全过程。 8.2 现实生活中有如下现象:某人的办公桌上放了红色和白色两部电话机、一台收录机,他正在听一段录音。试把下列各项与计算机的中断相对比: (1)电话铃响。 (2)判断是哪一部电话响铃。 (3)两部电话同时响铃时先接红色。 (4)按下录音机暂停键。 (5)接电话。 (6)按起录音机暂停键。
(7)电话铃响但不接电话。 (8)拔掉白色电话机的电话线。 (9)接听白色电话时红色电话机响铃,放下白色电话去接红色电话。 8.3 在例8.2的基础上,编写一个控制计算机的扬声器发声的子程序,以发出声音的频率和维持发声时间的长短为两个入口参数。 8.4 控制计算机上的扬声器发出音乐声,乐曲自选。 8.5 IRET指令与RET指令有什么异同?能否用RET 2或者RET指令代替中断服务程序的结束指令IRET?为什么? 8.6 为例8.4的时钟程序增加一项功能:每当整点时(即分钟数和秒钟数都为0)时,让扬声器产生一次报时声。
8.7 设计以中断方式驱动打印机的程序流程图。 8.8 比较4种输入输出方式的特点,并说明为什么使用直接方式的外设并不多。 8.9 说明一般接口的基本构成,分别列举三大部件的作用。 8.10 有下面的程序段: PUSH AX MOV AH,1 INT 21H MOV DL,AL POP AX 当系统执行到MOV DL,AL指令时,定时器产生了一次8号中断请求,且IF=1。试说明该程序段执行期间堆栈的变化情况。