❤️💕💕汇编语言目前仍在发挥着不可替代的作用,在效率上无可替代,在底层,学习linux内核,计算机外围设备和驱动,都离不开汇编。Myblog:http://nsddd.top
[TOC]
- 键盘输入
- 引发
9
号中断 - 执行
int 9
号中断例程
- 键盘上的每一个按键相当于一个开关,键盘中有一个芯片对键盘上的每一个键的开关状态进行扫描
- 按下一个键的操作
- 按下一个键时,开关接通,该芯片就产生一个扫描码,扫描码说明了按下的键在键盘上的位置。
- 扫描码被送入主板上的相关芯片的寄存器中,该寄存器的端口地址为60h。
- 松开按下的键的操作
- 也产生一个扫描码, 扫描码说明了松开的键在键盘上的位置。
- 松开按键时产生的扫描码也被送入60端口中。
可以看到扫描码在这里面的作用很重要
- 按下一个键时产生的扫描码称为通码
- 松开一个按键产生的扫描码称为断码
- 扫描码长度为一个字节,通码的第七位为0,断码的第七位为1
- 断码 = 通码 + 80h
例如:g键的通码为22H,断码为a2H(不同数的键码对应ASCII码)
通码:00100010 断码:10100010
键盘按下,会引发9号中断
-
键盘的输入到达
60H
端口时,相关的芯片就会向cpu
发出中断类型码为9
的可屏蔽中断信息。 -
cpu
检测到改中断信息后IF = 1
:则响应中断,引发中断过程,转去执行int 9
中断例程。
输入的字符键如何保存?
- 有BIOS键盘缓冲区
- BIOS键盘缓冲区:是系统启动后,BIOS用于存放
int 9
中断例程所接受的键盘输入的内存区- BIOS键盘缓冲区:可以存储15个键盘输入,一个键盘输入用一个字单元存放
- 高位字节存放扫描码
- 低位字节存放字符码
BIOS提供了int 9中断例程,用来进行基本的键盘输入处理,主要的动作如下:
-
读出60端口中的扫描码
-
根据扫描码分情况对待
-
如果是字符键的扫描码,将该扫描码和它所对应的字符码即ASCII码送入内存中的BIOS键盘缓冲区;
-
如果是控制键(如ctrl)和切换键(如CapsLock)的扫描码,则将其转变为状态字节(用二进制位记录控制键和切换键状态的字节)写入内存中存储 状态字节的单元;
-
-
对键盘系统进行相关的控制(比如说,向相关芯片发出应答信息)
BIOS键盘缓冲区是系统启动后,BIOS用于存放int 9号中断例程所接受的键盘输入的内存区。该内存区可以存储15个键盘输入,因为int 9中断例程除了接受扫描码外,还要产生和扫描码对应的字符码,所以在BIOS键盘缓冲区中,一个键盘输入用一个字单元存放,高位字节存放扫描码,低位字节存放字符码。
0040:17单元存储键盘状态字节,该字节记录了控制键和切换键的状态。
由以上内容我们知道,键盘输入的处理过程:
-
键盘上的芯片产生扫描码,
-
扫描码被送入60端口,
-
引导9号中断;
-
cpu执行int9中断例程处理键盘输入
在上面的过程中,前3步都是由硬件系统完成的。我们能改变的只有int 9中断处理程序。我们可以重新编写int9中断例程。按照自己的意图来处理键盘的输入,因为一个完整的键盘输入的处理要涉及一些硬件细节,针对这种情况,我们可以在自己编写的中断例程中调用BIOS的int9中断例程帮我们处理。
编程:在屏幕中间依次显示"a"“z”,并可以让人看清。在显示的过程中,按下Esc键后,改变显示的颜色。
依次显示"a""z"的代码如下:
assume cs:code
code segment
start:mov ax, 0b800h
mov es, ax
mov ah, 'a' ;操作显示屏
s1: mov es:[160*12+40*2], ah
inc ah ;a+1后比较
cmp ah, 'z'
jna s1 ;不相等就回滚 -- z不成立
mov ax, 4c00h
int 21h
code ends
end start
在上面代码的执行过程中,我们无法看清屏幕上字符的变化,因为cpu执行太快导致字符切换过快,因此我们想办法在每显示一个字符后延时一段时间,让人看清后再显示下一个字符,这里我们实现一个延时的子程序供主程序调用,代码如下:
delay: push ax
push dx
mov ax, 0
mov dx, 1000h ;//cpu循环执行10000 000h次
s2: sub ax, 1
sbb dx, 0
cmp ax, 0
jne s2
cmp dx, 0
jne s2
pop dx
pop ax
ret
键盘输入到达端口后,就会引发9号中断,cpu转去执行int 9
中断例程。我们可以编写int9中断例程,功能如下:
(1)从60
端口中读出键盘的输入;
(2)调用BIOS的int 9
中断例程,处理其他硬件细节;
(3)判断按键是否为Esc
的扫描码,如果是,则改变显示的颜色后返回;如果不是则直接返回。
步骤
1、从60
端口中读出键盘的输入
in al, 60h
2、调用BIOS的int 9
中断例程
-
在我们编写好
int 9
中断例程后,主程序必须将中断向量表中的int9中断例程的入口地址改为我们新写的中断处理程序的入口地址 -
在新的中断处理程序中调用原来的int9中断例程时,中断向量表中的int9号中断例程的入口地址已不是原来的int9中断例程的地址,所以我们不能使用int指令直接调用。
解决方法:保存原中断例程入口和地址
要能在我们新写的中断例程中调用原来的中断例程,就必须在将中断向量表中的中断例程的入口地址改为新地址之前,将原来的入口地址保存起来,这样在需要调用的时候才能找到。
当然不能使用指令int9来调用,我们可以使用别的指令来对int指令进行模拟,从而实现对中断例程的调用。
我们知道,int指令在执行的时候,cpu进行下面的工作。
- 取中断类型码n;
- 标志寄存器入栈
- IF=0,TF=0;
- CS、IP入栈
- (IP)=(n4), (CS=n4+2)
取中断类型码是为了定位中断例程的入口地址,在我们自己写的中断例程中已知道地址,因此不需要做第一步,假设要调用的中断
例程的入口地址被保存在ds:0和ds:2单元,我们将用以下步骤来模拟int过程
- 标志寄存器入栈
- IF=0,TF=0;
- CS、IP入栈
- (IP)=(ds16+0), (CS=ds16+2)
注意到第三步和第四步的功能和call dword ptr ds:[0]的功能一样,call dword ptr ds:[0]的功能也是 $$ push cs, push ip, (IP)=(ds16+0), (CS=ds16+2) $$ 所以int的模拟过程变为:
- 标志寄存器入栈
- IF=0,TF=0
- call dword ptr ds:[0]