到目前为止,我们的操作系统只能输出而不能输入。本章将要实现的是键盘驱动,其能让我们的操作系统接收键盘输入。

15.1 键盘驱动的原理

当按下键盘上的键时,发生了什么呢?原来,每当按下键盘上的键,键盘都会发起至少一次键盘中断;每当一个键弹起时,键盘又会发起至少一次键盘中断;如果一直按住一个键不松手,键盘就会连续不断的发起键盘中断。

键盘接在8259A主片的第二个接口上,所以,想要接收到键盘中断,就需要取消对这个接口的中断屏蔽。

当一个键被按下或弹起后,可以从0x60端口读取到一个数字,其被称为键盘扫描码(Keyboard scancode)。0x60端口是一个8位的端口,但键盘扫描码不一定是8位的,还有可能是16位的,甚至更多。对于此类多字节的键盘扫描码,键盘会连续多次发起中断,每个字节发起一次。

在计算机的发展过程中,键盘扫描码一共出现了三套,但我们无需关注此事,这是因为不管键盘实际使用的是哪一套键盘扫描码,其最终都会被转码为第一套键盘扫描码,然后存储到0x60端口以供读取。

上文提到,键盘上的键被按下和弹起时,都会发起中断。对于同一个键,其被按下和弹起时产生的键盘扫描码是不同的,分别被称为通码(Make code)和断码(Break code)。

完整的键盘扫描码表可以参考这个网页:https://wiki.osdev.org/PS/2_Keyboard#Scan_Code_Set_1。此外,笔者发现一些书籍和互联网上关于`/~这个键的断码常有误,请读者知悉。

我们的操作系统只支持主键盘上的键盘扫描码,如下表所示:

按键通码断码
ESC0x10x81
10x20x82
20x30x83
30x40x84
40x50x85
50x60x86
60x70x87
70x80x88
80x90x89
90xa0x8a
00xb0x8b
-0xc0x8c
=0xd0x8d
Backspace0xe0x8e
Tab0xf0x8f
Q0x100x90
W0x110x91
E0x120x92
R0x130x93
T0x140x94
Y0x150x95
U0x160x96
I0x170x97
O0x180x98
P0x190x99
[0x1a0x9a
]0x1b0x9b
Enter0x1c0x9c
Left Ctrl0x1d0x9d
A0x1e0x9e
S0x1f0x9f
D0x200xa0
F0x210xa1
G0x220xa2
H0x230xa3
J0x240xa4
K0x250xa5
L0x260xa6
;0x270xa7
'0x280xa8
`0x290xa9
Left Shift0x2a0xaa
\0x2b0xab
Z0x2c0xac
X0x2d0xad
C0x2e0xae
V0x2f0xaf
B0x300xb0
N0x310xb1
M0x320xb2
,0x330xb3
.0x340xb4
/0x350xb5
Right Shift0x360xb6
*(小键盘)0x370xb7
Left Alt0x380xb8
Space0x390xb9
CapsLock0x3a0xba

从上表可以看出:

  1. 所有的通码与断码之间都相差0x80
  2. 键盘只负责产生键盘扫描码,不处理大小写,上挡键等。这部分功能由键盘驱动完成

15.2 键盘驱动的实现

键盘驱动的实现分为以下三个步骤:

  1. 向8259A发送中断响应信号
  2. 0x60端口读取键盘扫描码
  3. 实现一个函数,处理键盘扫描码。本章中,键盘驱动的目标是打印输入的键(如果输入的键是可打印字符的话)

请看本章代码15/Keyboard.h

第5行,声明了keyboardDriver函数。

接下来,请看本章代码15/Keyboard.hpp

第7\~15行,定义了__KEYBOARD_MAP_LIST变量,该变量定义了键盘扫描码和字符之间的关系。这是一个二维数组,第一维的索引值使用键盘扫描码;第二维的索引值使用0或1,表示上档状态。对于那些不可打印的字符,如Shift键等,在表格中以{'\0', '\0'}占位。

第17\~18行,定义了两个布尔值,分别用于表示Shift键和CapsLock键的状态。

keyboardDriver函数是键盘驱动的核心,其用于处理键盘扫描码。

第22\~25行,处理Shift键。Shift键有左右两个,其扫描码不同;并且,无论是通码还是断码,都意味着Shift键的状态发生了一次改变。

第26\~29行,处理CapsLock键。CapsLock键与Shift键不同,它是按一下切换一次状态。所以,只需要关注CapsLock键的通码。

第30\~41行,处理其他键。Shift键与CapsLock键混合在一起的逻辑比较复杂,描述如下:

  1. Shift键影响所有的键
  2. CapsLock键只影响字母键
  3. 这两个键之间是异或关系,只能二选一。例如:如果CapsLock键已经被按下,再按住Shift键,打出的字母就是小写字母

第32\~35行,使用一个很长的逻辑表达式,将键盘扫描码转换成ASCII码。

第37\~40行,判断这个ASCII码是否可打印,如果是,就打印这个字符。

接下来,请看本章代码15/Int.s

第5行,声明了外部链接的keyboardDriver函数。

第37行,将发送给8259A主片的中断屏蔽掩码从0xfe改成了0xfc,这样就打开了键盘中断。

第93行,删除了intTmpl 0x21宏展开,其将被intKeyboard函数代替。

intKeyboard函数是键盘中断处理函数。

第157\~159行,向8259A发送中断响应信号。

第161\~164行,从0x60端口读取键盘扫描码,然后调用keyboardDriver函数。

第168行,使用iret指令从中断返回。

第249行,将intKeyboard函数安装在intList中,从而,键盘驱动就会被Int.hpp中的__installIDT函数安装到IDT中。

15.3 测试

本章代码15/Kernel.c用于测试键盘驱动。


樱雨楼
26 声望1 粉丝

Stay Gold