中断服务程序
采用查询方式进行I/O操作,是由处理器主动执行程序实现的。如果处理器没有查询外设,即使外设准备好数据,交换也无法进行。对于需要及时交换数据的外设,可以变被动为主动,采用中断请求向处理器提出要求;这时处理器执行事先设计好的中断服务程序,在中断服务程序中实现数据交换,这就是程序中断输入输出方式。
8086可以处理256种中断,分为内部、外部两种(参见
http://www.cublog.cn/u/8327/showart.php?id=47625)其中外部可屏蔽中断用于与外设进行数据交换。但是从8086 CPU获得中断向量号开始到进入中断服务程序的过程,不同类型的中断时没有差别的。
1、内部中断服务程序
主程序通过中断调用指令INT n执行内部中断服务程序,其实质相当于子程序调用。所以编写内部中断服务程序与编写子程序雷同,都是利用过程定义伪指令PROC/ENDP。不同的是,进入中断服务程序后通常都要执行STI指令开放可屏蔽中断,最后执行IRET指令返回调用程序。内部中断通常采用寄存器传递参数。
主程序在调用内部中断程序之前,必须修改中断向量(在中断向量表中设置中断服务程序的入口地址),使其指向相应的中断服务程序。修改中断向量可以自编一个这样的程序段或利用DOS功能调用:
.DOS功能调用INT 21H的设置中断向量功能:AH=25H
入口参数:AL=中断向量号,
DS:DX=中断服务程序的入口地址(段基地址:偏移地址)
中断服务程序如果只被某个应用程序使用,那么应用程序返回DOS前,也要修改中断向量,使系统恢复原状态。这只要在设置中断向量之前,首先读取并保存元中断向量即可。可编程实现或利用DOS功能调用:
. DOS功能调用INT 21H的获得中断向量功能:AH=35H
入口参数:AL=中断向量号
出口参数:ES:BX=中断服务程序的入口地址(段基地址:偏移地址)
例33.1:内部中断服务程序
分析:修改中断向量号80H的服务程序,显示一段信息。
.model small
.stack
.data
intoff dw ?
intseg dw ?
.code
.startup
mov ax,3580h
int 21h
mov intoff,bx
mov intseg,es
push ds
mov dx,offset newint80h
mov ax,seg newint80h
mov ds,ax
mov ax,2580h
int 21h
pop ds
;
int 80h
;
mov dx,intoff
mov ax,intseg
mov ds,ax
mov ax,2580h
int 21h
.exit 0
newint80h proc
sti
push ax
push bx
push cx
push si
mov si,offset intmsg
mov cx,sizeof intmsg
diap: mov al,cs:[si]
mov bx,0
mov ah,0eh
int 10h
inc si
loop disp
pop si
pop cx
pop bx
pop ax
iret
intmsg db'I am Great!',0dh,0ah
newint80h endp
end
该程序首先读取并保存中断80H的原中断向量,然后设置新中断向量。当程序不再需要这个中断服务程序,就将保存的原中断向量恢复,这样该程序返回DOS后没有改变系统状态。
2、驻留中断服务程序
用户的中断服务程序如果要让其他程序使用,必须驻留在系统主存中。这就形成驻留TSR(Terminate and Stay Resident)程序。实现程序驻留并不难,利用DOS功能调用31h代替4ch终止程序并返回DOS:
.DOS功能调用INT 21H的驻留返回功能:AH=31H
入口参数:AL=返回代码(用0表示没有错误),
DX=程序驻留的容量(单位为节,1节=16个字节)
程序驻留之后还有诸如撤销、激活等深入的问题,这个以后探讨。
例33.2:驻留中断服务程序
分析:将上面的INT 80H内部中断服务程序驻留。小型驻留程序一般写成COM格式程序,驻留部分要写在前面。
.model tiny
.code
.startup
jmp start
newint80h proc
sti
push ax
push bx
push cx
push si
mov si,offset intmsg
mov cx,sizeof intmsg
diap: mov al,cs:[si]
mov bx,0
mov ah,0eh
int 10h
inc si
loop disp
pop si
pop cx
pop bx
pop ax
iret
intmsg db'I am Great!',0dh,0ah
newint80h endp
start: mov ax,cs
mov ds,cx
mov dx,offset newint80h
mov ax,2580h
int 21h
int 80h
mov dx,offset istmsg
mov ah,9
int 21h
mov dx,(offset start)+15
mov cl,4
shr dx,cl
mov ax,3100h
int 21h
istmsg db 'INT 80H is installed!',0dh,0ah,'$'
end
该程序执行后,驻留的中断服务程序常驻内存,而起安装作用的主程序从内存中消失。
3、外部可屏蔽中断服务程序
外设采用中断方式与处理器交换信息是利用外部可屏蔽中断实现的,在8086CPU系统中,可屏蔽中断还需要借助中断控制器(如Intel 8259A)管理。所以,可屏蔽中断才是真正意义上的“中断”;编程可屏蔽中断就具有一定的特殊性。需要注意如下几点:
.发送中断结束命令
可屏蔽中断服务程序在执行中断返回指令前,应向中断控制器发送中断结束EOI命令;否则,以后将屏蔽对同级中断和低级中断的响应。方法如下:
mov al,20h ;写入EOI命令
out 20h,al ;20h是中断控制器的一个I/O地址
.不能采用寄存器传递参数
外部中断是随时发生的。所以系统进入服务程序时,除CS和IP寄存器外,当前的运行状态,包括其他寄存器都是不可知的,想通过寄存器传递参数显然不行。但是,为了中断处理结束后能正确返回断点执行被中断的主程序,必须保存好中断服务程序中使用的所有寄存器,在结束前恢复。
.不要使用DOS系统功能调用INT 21H
外部中断可能引起子程序的重入。DOS内核是不可重入的,所以不能使用DOS系统调用。中断服务程序若要控制I/O设备,最好调用ROM-BIOS功能或者对I/O接口直接编程。
.中断服务程序尽量短小
外部中断的实时性很强,应主要处理较急迫的事物,因此中断服务程序时间应该尽量短,能放在主程序完成的任务,就不要由中断服务完成。这样可以避免干扰其他中断设备的工作,例如:PC机上影响系统日时钟计时的准确性。
另一方面,主程序除需要修改中断向量外,还要注意如下几点:
.控制CPU的中断允许标志IF
可屏蔽中断的响应受中断标志控制,程序中通过关中断指令CLI禁止可屏蔽中断,而通过开中断指令STI允许可屏蔽中断。当不需要可屏蔽中断或程序不能被外部中断时,就必须关中断,防止不可预测的后果,而其他时间则要开中断,以便及时响应中断,为外设提供服务。
例如,设置好可屏蔽中断服务程序之前和为屏蔽中断服务程序提供初始值等时间,不能响应中断,关中断。在此之后,要开中断,另外进入中断服务程序后,要马上开中断,以允许较高级的中断。
.设置中断屏蔽寄存器IMR
可屏蔽中断还通过中断控制寄存器管理,所以某个可屏蔽中断的响应与否还受控于中断屏蔽寄存器。CPU的中断标志IF是控制所有可屏蔽中断的,而屏蔽控制寄存器是分别控制某个可屏蔽中断源的。在PC微机中,中断屏蔽寄存器的端口地址是21h,中断屏蔽寄存器某位Di为0时,就允许相应的中断IRQi,例如:
in al,21h ;读出中断屏蔽寄存器IMR
and al,0fch ;只允许IRQ0和IRQ1,其他不变
out 21h,al ;写入中断屏蔽寄存器IMR
在主程序和中断服务程序中都可以通过控制中断屏蔽寄存器的有关位,随时允许或禁止对应的中断。同样,为了应用程序返回后,恢复原状态,在修改IMR前,要保护,程序退出前,要恢复。
PC微机中,键盘采用中断方式向系统提供按键的扫描码。每当用户按下一个键时,键盘就向系统申请键盘中断(对应IRQ1,中断向量号9)。在中断服务程序中,通过数据端口60H读取该键的接通扫描码,然后通过控制端口61h(还记得吧?上一篇讲述程序直接控制输入输出中在讲到定时电路讲到过)的最高位D7应答键盘。而当放开按下的键时,键盘也要产生9号可屏蔽中断,只是读取的是断开扫描码。如果一直按着某个键,则键盘以一个固定频率产生中断,不断提供接通扫描码。
PC微机中当然有9号中断调用,还有为程序员使用的ROM-BIOS功能调用(INT 16H)和DOS功能调用(如01H和0AH字功能)。现在我们编写一个09号可屏蔽中断服务程序,实现显示每个按键的扫描码。
例33.3:外部可屏蔽中断服务程序
分析:为了能够返回DOS,程序设计当按下ESC键时退出。这里采用固定存储单元在主程序和中断服务程序之间传递参数。
.model small
.stack
.data
esccode db 0 ;用于保存ESC键的断开扫描码
.code
.startup
mov ax,3509
int 21h
push es
push bx ;保存原中断向量内容
cli ;关中断,以防止此时产生键盘中断
push ds ;设置新中断向量
mov ax,2509h
mov dx,seg scancode
mov ds,dx
mov dx,offset scancode
int 21h
pop ds
in al,21h ;读出IMR
push ax ;保存原IMR
and al,0fdh ;允许键盘中断(D1),其他不变
out 21h,al ;设置新IMR
mov byte ptr esccode,0 ;设置ESC键初值
sti ;开中断
waiting:
cmp byte ptr esccode,81h ;循环等待按下并释放ESC键
jne waiting ;中断服务程序设置esccode单元内容
cli
pop ax ;恢复原IMR
out 21h,al
pop dx ;恢复原中断向量
pop ds
mov ax,2509h
int 21h
sti
.exit 0
scancode proc ;新的键盘中断服务程序
sti
push ax
push bx
in al,60h ;读取扫描码
push ax
in al,61h ;通过PB7应答键盘
or al,80h
out 61h,al ;使PB7=1
and al,7fh
out 61h,al ;使PB7=0
pop ax
cmp al,81h
jne scan1 ;不是ESC键断开扫描码,则转移到显示部分
push ds ;是ESC键断开扫描码,则设置esccode单元
mov bx,@data ;设置数据段地址
mov ds,bx
mov esccode,al ;设置esccode单元为其扫描码
pop ds
scan1: ;显示扫描码
push ax
shr al,1
shr al,1
shr al,1
shr al,1
cmp al,0ah
jb scan2
add al,7
scan2:
add al,30h
mov bx,0
mov ah,0eh
int 10h
pop ax
and al,0fh
cmp al,0ah
jb scan3
add al,7
scan3:
add al,30h
mov ah,0eh
int 10h
mov ax,0e20h
int 10h
mov ax,0e20h
int 10h
mov al,20h
out 20h,al
pop bx
pop ax
iret ;中断返回
scancode endp
end
阅读(2903) | 评论(3) | 转发(0) |