1. 预备知识
-
除前面介绍的除法错误、单步执行、int 指令等来自 CPU 内部的中断,外部中断共分为两类:可屏蔽中断,CPU 根据标志位 IF 的值来决定是否响应该中断,如果 IF=1 则响应该中断,否则不响应;不可屏蔽中断,CPU 必须响应该中断,中断类型码固定为 2。
-
键盘上的每个按键相当于一个开关,键盘中的芯片对按键的开关状态进行扫描。按键按下和松开将产生对应的扫描码,扫描码描述了键在键盘中的位置。
-
一般将键按下时产生的码称为通码,松开时产生的码称为断码,产生的通码和断码均送入 60h 端口中。扫描码的长度为 1 个字节,且满足断码=通码+80h。键盘上部分键的通码如下图:
- 以键盘输入为例,PC 机处理外部中断的流程包括:(1)键盘按键按下和松开时产生的扫描码被送入 60h 端口;(2)相关芯片向 CPU 发出中断类型码为 9 的可屏蔽中断信息;(3)执行 9 号中断例程。
2. 实验任务 1:延时显示
在屏幕上依次显示 'a'~'z',并可以让人看清。
2.1 实验分析
在屏幕上依次显示 'a'~'z':
assume cs:code
code segment
start:
mov ax,0b800h
mov es,ax
mov ah,'a'
s:
mov es:[160*12+40*2],ah
inc ah
cmp ah,'z'
jna s
mov ax,4c00h
int 21h
code ends
end start
程序运行结果如下:
由结果可知,我们只能在屏幕看到字母 z 的显示。这是由于在一个字母显示完成后,CPU 继而切换至下一个字母的显示程序,切换时间太短,导致无法看清中间字母的显示。
我们让当前字母显示完成后,延时一段时间,看清后再显示下一个字母。解决办法是,在显示完当前字母后让 CPU 执行一段时间的空循环。为保证足够长的延时,用两个 16 位寄存器 AX 和 DX 来存放 32 位的循环次数。实现延时功能的子程序为:
delay: ;共执行循环10h*1000h次
push ax
push dx ;保护现场
mov dx,10h
mov ax,0
help:
sub ax,1 ;(AX)=0FFFFh
sbb dx,0 ;无符号运算(AX)=(AX)-1使得CF=1,本句相当于(DX)=(DX)-0-CF(1)=0Fh
cmp ax,0
jne help ;如果(AX)不等于零则转移至s1,循环10000h次
cmp dx,0
jne help ;如果(DX)不等于零则转移至s1,0Fh不等于零而(AX)等于零使得sub ax,1又等于0FFFFh
pop dx
pop ax ;恢复现场
ret
在上述程序中,sub 减法得到的结果为 0FFFFh,第一个 jne 固定执行 10000h 次循环。sbb 为带借位的减法指令,由于 sub 减法产生了借位,sbb 实际执行 (DX)=(DX)-1。执行第二个 jne 时 AX 的值为 0,所以第二个 jne 跳转至 help 后,AX 又变为 0FFFFh。以此,使用两个 16 位寄存器 AX 和 DX 共执行 10000h*(DX) 次循环。整体代码为:
assume cs:code
code segment
start:
mov ax,0b800h
mov es,ax
mov ah,'a'
s:
mov es:[160*12+40*2],ah
call delay
inc ah
cmp ah,'z'
jna s
mov ax,4c00h
int 21h
delay: ;共执行循环10h*10000h次
push ax
push dx ;保护现场
mov dx,10h
mov ax,0
help:
sub ax,1 ;(AX)=0FFFFh
sbb dx,0 ;无符号运算(AX)=(AX)-1使得CF=1,本句相当于(DX)=(DX)-0-CF(1)=0Fh
cmp ax,0
jne help ;如果(AX)不等于零则转移至s1,循环10000h次
cmp dx,0
jne help ;0Fh不等于零而(AX)等于零使得sub ax,1又等于0FFFFh
pop dx
pop ax ;恢复现场
ret
code ends
end start
2.2 实验结果
3. 实验任务 2:键盘控制字符属性
在上一任务的基础上,在字母显示过程中,按下 Esc 键改变字母的颜色。
3.1 实验分析
根据预备知识的第四条,重写 int 9 中断例程,满足以下功能:
-
从 60h 端口读取键盘输入
-
调用 int 9 中断例程
-
基于扫描码判断当前是否按下 Esc 键,如果按下则改变字符颜色并返回;否则直接返回
(1)基于 in 指令从 60h 端口读取键盘输入:
in al,60h
(2)调用 int 9 中断例程。我们编写完新的 int 9 中断处理程序后,需设置中断向量表的对应项。由于在新中断处理程序中需处理键盘输入,我们仍需调用旧中断处理程序。此时,无法使用 int 指令调用,因为对应中断向量已修改为新中断处理程序的入口地址。
所以,在写入新中断处理程序的地址前,首先保存旧中断向量的入口地址,将其保存在内存的 ds:0 和 ds:2 单元。然后,在调用旧中断向量时模拟 int 指令的执行状态:
-
取中断类型码
-
标志寄存器入栈
-
IF=0、TF=0
-
CS 和 IP 入栈
-
(IP)=n*4,(CS)=(n*4+2)
上述第一步不用模拟,后四步可使用以下代码代替:
pushf ;标志寄存器入栈
pushf
pop ax
and ah,11111100b ;TF和IF为第9位和第8位
push ax
popf ;TF=0、IF=0
call dword ptr ds:[0] ;CS和IP入栈,且目的段地址由内存单元的高地址给出、偏移地址由低地址给出
(3)如果按下 Esc 键,则改变字母的显示颜色。字母的显示单元为 b800:160*12+40*2,则其属性单元是 b800:160*12+40*2+1。
mov ax,0b800h
mov es,ax
inc byte ptr es:[160*12+40*2+1]
(4)在程序返回前,将中断向量表的 9 号中断向量恢复为原中断处理程序的入口地址。否则程序返回后,别的程序无法使用 int 指令调用。
mov ax,0
mov es,ax
push ds:[0]
pop es:[9*4]
push ds:[2]
pop es:[9*4+2]
(5)如果在设置中断例程的段地址和偏移地址之间发生了键盘中断,则 CPU 将转去一个错误的地址执行指令。而键盘中断为可屏蔽中断,可通过 IF 位控制 CPU 是否响应该中断。所以,在设置地址前将 IF 设置为 0 表示不响应中断(cli 指令)、设置完成后设置 IF=1(sti 指令)以保证正确设置地址。
cli
mov ax,0
mov es,ax
push ds:[0]
pop es:[9*4]
push ds:[2]
pop es:[9*4+2]
sti
综上,整体代码为:
assume cs:code
stack segment
db 128 dup (0)
stack ends
data segment
dw 0,0
data ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,128
mov ax,data
mov ds,ax
mov ax,0
mov es,ax
push es:[9*4]
pop ds:[0]
push es:[9*4+2]
pop ds:[2] ;将原来int 9中断例程的入口地址保存在ds:0和ds:2单元
cli
mov word ptr es:[9*4],offset int9
;在中断向量表中设置新的int 9中断例程的偏移地址
mov es:[9*4+2],cs
;在中断向量表中设置新的int 9中断例程的段地址
sti
mov ax,0b800h
mov es,ax
mov ah,'a'
s:
mov es:[160*12+40*2],ah
call delay ;调用子程序,让CPU休眠
inc ah
cmp ah,'z'
jna s
cli
mov ax,0
mov es,ax
push ds:[0]
pop es:[9*4]
push ds:[2]
pop es:[9*4+2] ;恢复原int 9中断例程的地址
sti
mov ax,4c00h
int 21h
delay: ;共执行循环10h*10000h次
push ax
push dx ;保护现场
mov dx,10h
mov ax,0
help:
sub ax,1 ;(AX)=0FFFFh
sbb dx,0 ;无符号运算(AX)=(AX)-1使得CF=1,本句相当于(DX)=(DX)-0-CF(1)=0Fh
cmp ax,0
jne help ;如果(AX)不等于零则转移至s1,循环10000h次
cmp dx,0
jne help ;如果(DX)不等于零则转移至s1,0Fh不等于零而(AX)等于零使得sub ax,1又等于0FFFFh
pop dx
pop ax ;恢复现场
ret
int9:
push ax
push bx
push es ;保护现场
in al,60h
pushf ;标志寄存器入栈
;由于在上述使用60h端口响应中断时,TF和IF的值已经设置为0,下面代码可以去除
;pushf
;pop bx
;and bh,11111100b ;TF和IF为第9位和第8位
;push bx
;popf ;TF=0、IF=0
call dword ptr ds:[0]
;CS和IP入栈,且目的CS由内存单元高地址给出、IP由低地址给出
cmp al,1 ;esc的通码为1
jne int9ret
mov ax,0b800h
mov es,ax
inc byte ptr es:[160*12+40*2+1]
;更改字母的显示颜色
int9ret:
pop es
pop bx
pop ax ;恢复现场
iret
code ends
end start
3.2 实验结果
4. 总结
-
本文以键盘输入为例介绍了外部中断的处理流程,键盘通过将按键扫描码送入 60h 端口进而触发 9 号中断完成相应的功能
-
在编写 9 号中断处理程序时,应保存原中断处理程序的地址以在新中断处理程序中调用,且在程序返回前恢复中断向量表的对应项
-
参考:汇编语言/王爽著.——北京:清华大学出版社,2003