Skip to content

Latest commit

 

History

History
230 lines (136 loc) · 7.28 KB

File metadata and controls

230 lines (136 loc) · 7.28 KB

第38节 PC键盘的处理过程

❤️💕💕汇编语言目前仍在发挥着不可替代的作用,在效率上无可替代,在底层,学习linux内核,计算机外围设备和驱动,都离不开汇编。Myblog:http://nsddd.top


[TOC]

键盘输入的处理过程

  1. 键盘输入
  2. 引发9号中断
  3. 执行int 9号中断例程

键盘输入

  • 键盘上的每一个按键相当于一个开关,键盘中有一个芯片对键盘上的每一个键的开关状态进行扫描
  • 按下一个键的操作
    • 按下一个键时,开关接通,该芯片就产生一个扫描码,扫描码说明了按下的键在键盘上的位置。
    • 扫描码被送入主板上的相关芯片的寄存器中,该寄存器的端口地址为60h。
  • 松开按下的键的操作
    • 也产生一个扫描码, 扫描码说明了松开的键在键盘上的位置。
    • 松开按键时产生的扫描码也被送入60端口中。

可以看到扫描码在这里面的作用很重要

  • 按下一个键时产生的扫描码称为通码
  • 松开一个按键产生的扫描码称为断码
  • 扫描码长度为一个字节,通码的第七位为0,断码的第七位为1
    • 断码 = 通码 + 80h

例如:g键的通码为22H,断码为a2H(不同数的键码对应ASCII码)

通码:00100010
断码:10100010

引发9号中断

键盘按下,会引发9号中断

  • 键盘的输入到达60H端口时,相关的芯片就会向cpu发出中断类型码为9的可屏蔽中断信息。

  • cpu检测到改中断信息后IF = 1:则响应中断,引发中断过程,转去执行int 9中断例程。

输入的字符键如何保存?

  1. 有BIOS键盘缓冲区
  2. BIOS键盘缓冲区:是系统启动后,BIOS用于存放int 9中断例程所接受的键盘输入的内存区
  3. BIOS键盘缓冲区:可以存储15个键盘输入,一个键盘输入用一个字单元存放
    1. 高位字节存放扫描码
    2. 低位字节存放字符码

指令int 9中断例程

BIOS提供了int 9中断例程,用来进行基本的键盘输入处理,主要的动作如下:

  1. 读出60端口中的扫描码

  2. 根据扫描码分情况对待

    • 如果是字符键的扫描码,将该扫描码和它所对应的字符码即ASCII码送入内存中的BIOS键盘缓冲区;

    • 如果是控制键(如ctrl)和切换键(如CapsLock)的扫描码,则将其转变为状态字节(用二进制位记录控制键和切换键状态的字节)写入内存中存储 状态字节的单元;

  3. 对键盘系统进行相关的控制(比如说,向相关芯片发出应答信息)

BIOS键盘缓冲区是系统启动后,BIOS用于存放int 9号中断例程所接受的键盘输入的内存区。该内存区可以存储15个键盘输入,因为int 9中断例程除了接受扫描码外,还要产生和扫描码对应的字符码,所以在BIOS键盘缓冲区中,一个键盘输入用一个字单元存放,高位字节存放扫描码,低位字节存放字符码。

0040:17单元存储键盘状态字节,该字节记录了控制键和切换键的状态。

定制键盘输入处理

由以上内容我们知道,键盘输入的处理过程:

  1. 键盘上的芯片产生扫描码,

  2. 扫描码被送入60端口,

  3. 引导9号中断;

  4. 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进行下面的工作。

  1. 取中断类型码n;
  2. 标志寄存器入栈
  3. IF=0,TF=0;
  4. CS、IP入栈
  5. (IP)=(n4), (CS=n4+2)

取中断类型码是为了定位中断例程的入口地址,在我们自己写的中断例程中已知道地址,因此不需要做第一步,假设要调用的中断

例程的入口地址被保存在ds:0和ds:2单元,我们将用以下步骤来模拟int过程

  1. 标志寄存器入栈
  2. IF=0,TF=0;
  3. CS、IP入栈
  4. (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的模拟过程变为:

  1. 标志寄存器入栈
  2. IF=0,TF=0
  3. call dword ptr ds:[0]

END 链接