(二十六)《汇编语言(王爽)》 | 检测点 15.1


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 表示可发生中断。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值