1. 预备知识
- 以键盘输入为例,首先介绍 PC 机处理外设基本输入的流程。(1)键盘输入。键盘上的每个键相当于一个开关,键盘中的芯片对每个键的开关进行扫描。按下键时,产生的扫描码描述按下键在键盘中的位置,该扫描码称为通码;松开键时,产生的扫描码描述松开键在键盘中的位置,该扫描码称为断码。相关处理端口为 60H,且满足断码 = 通码 + 80H。(2)引发 9 号中断。键盘的输入到达 60H 端口后,相关芯片回向 CPU 发出中断类型码为 9 的可屏蔽中断。(3)执行 int 9 中断例程。
- 综上,键盘输入的处理过程为:(1)键盘产生扫描码;(2)扫描码送入 60h 端口;(3)引发 9 号中断;(4)CPU 执行 int 9 中断例程处理键盘输入。
- 编程,在屏幕中间依次显示 ‘a’ ~ ‘z’,并可以让人看清。在显示的过程中,按下 esc 键后,改变显示的颜色。
首先,编写在屏幕显示 ‘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
如图,我们无法看清屏幕上的显示,因为一个字母显示完成后,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,循环0FFFFh次
cmp dx,0
jne help ;0Fh不等于零而(AX)等于零使得sub ax,1又等于0FFFFh
pop dx
pop ax ;恢复现场
ret
sub 减法得到的结果为 0FFFFh,第一个 jne 固定执行 0FFFFh 次循环。对于 sbb 带借位的减法指令,由于上一减法产生借位,sbb 实际上执行 (DX)=(DX)-1。执行第二个 jne 时 AX 的值为 0,所以第二个 jne 跳转至 help 后,AX 内容又变为 0FFFFh。以此,sub 和 sbb 的配合,使 CPU 共执行 0FFFFh*(DX) 次循环。
此时,调用子程序 delay 即可看清每个字母的显示。现完成后续功能按下 esc 键后,改变显示的颜色。键盘输入到达 60H 端口后,引发 9 号中断,CPU 转去执行 int 9 中断例程。则,我们可以编写 int 9 中断例程,功能如下:
- 从 60H 端口读出键盘的输入
- 调用 int 9 中断例程
- 判断是否为 esc 的扫描码,如果是,则改变显示颜色后返回;否则直接返回
(1)利用 in 指令从 60H 端口读出键盘的输入:
in al,60h
(2)调用 int 9 中断例程。我们编写的中断处理程序为新的 int 9 中断例程,需将中断向量表中对应入口地址改为新中断例程的入口地址。在新的中断处理程序中需调用原 int 9 中断例程,保存其段地址和偏移地址以供后续使用。
后续使用保存的段地址和偏移地址时,需模拟 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入栈,且目的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]
最后一个问题,要在程序返回前,将中断向量表中 int 9 中断例程的入口地址恢复为原地址。否则程序返回后,别的程序无法使用键盘:
mov ax,0
mov es,ax
push ds:[0]
pop es:[9*4]
push ds:[2]
pop es:[9*4+2]
综上,整体代码为:
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单元
mov word ptr es:[9*4],offset int9
;在中断向量表中设置新的int 9中断例程的偏移地址
mov es:[9*4+2],cs
;在中断向量表中设置新的int 9中断例程的段地址
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
mov ax,0
mov es,ax
push ds:[0]
pop es:[9*4]
push ds:[2]
pop es:[9*4+2] ;恢复原int 9中断例程的地址
mov ax,4c00h
int 21h
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,循环0FFFFh次
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 ;标志寄存器入栈
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
程序运行结果如下图:
2. 检测点 15.1
(1)分析上面 int 9 中断例程,看看是否可以精简?
在 int 9 中断例程中,模拟 int 指令调用原 int 9 中断例程的程序段是可以精简的,因为在进入中断例程后,IF 和 TF 都已经置 0,没有必要再设置。对于程序段:
pushf
pushf
pop ax
and ah,11111100b
push ax
popf
call dword ptr ds:[0]
- 因为 int 指令的其中一个步骤是将 IF 和 TF 置零,所以中间将 IF 和 TF 置零的部分为重复操作。可将上述代码精简为:
pushf
call dword ptr ds:[0]
(2)分析上述程序的主程序部分,看看有什么潜在问题?
在主程序中,如果在设置 int 9 中断例程的段地址和偏移地址之间发生了键盘中断,则 CPU 将转去一个错误的地址执行,将发生错误。找出这样的程序段,并改写它们。 相关代码部分为:
mov word ptr es:[9*4],offset int9
;在中断向量表中设置新的int 9中断例程的偏移地址
------------ 发生键盘中断,导致段地址设置错误 ------------
mov es:[9*4+2],cs
;在中断向量表中设置新的int 9中断例程的段地址
和:
push ds:[0]
pop es:[9*4]
------------ 发生键盘中断,导致段地址设置错误 ------------
push ds:[2]
pop es:[9*4+2] ;恢复原int 9中断例程的地址
- 来自键盘的中断是一种可屏蔽中断,CPU 根据标志寄存器 IF 的值来决定是否响应该中断,如果为 1 则响应,为 0 则不响应。
- 在汇编语言中,cli 指令表示不可发生中断,sti 表示可发生中断。在上述设置地址前加上 cli,设置地址后加上 sti 即可避免因中断造成的地址设置错误。
3. 总结
- 以键盘输入为例,本文介绍 PC 机处理外设基本输入的流程,包括键盘输入、引发 9 号中断、执行 int 9 中断例程三个步骤。
- 使 CPU 运行一定次数的空循环可延时屏幕显示。
- 指令 cli 不可发生中断,sti 表示可发生中断。
|