首页 > 其他分享 >一个操作系统的设计与实现——第15章 键盘驱动(上)

一个操作系统的设计与实现——第15章 键盘驱动(上)

时间:2023-11-12 10:12:55浏览次数:35  
标签:15 操作系统 中断 Shift 扫描 键盘 CapsLock

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

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。此外,笔者发现一些书籍和互联网上关于```/~``这个键的断码常有误,请读者知悉。

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

按键 通码 断码
ESC 0x1 0x81
1 0x2 0x82
2 0x3 0x83
3 0x4 0x84
4 0x5 0x85
5 0x6 0x86
6 0x7 0x87
7 0x8 0x88
8 0x9 0x89
9 0xa 0x8a
0 0xb 0x8b
- 0xc 0x8c
= 0xd 0x8d
Backspace 0xe 0x8e
Tab 0xf 0x8f
Q 0x10 0x90
W 0x11 0x91
E 0x12 0x92
R 0x13 0x93
T 0x14 0x94
Y 0x15 0x95
U 0x16 0x96
I 0x17 0x97
O 0x18 0x98
P 0x19 0x99
[ 0x1a 0x9a
] 0x1b 0x9b
Enter 0x1c 0x9c
Left Ctrl 0x1d 0x9d
A 0x1e 0x9e
S 0x1f 0x9f
D 0x20 0xa0
F 0x21 0xa1
G 0x22 0xa2
H 0x23 0xa3
J 0x24 0xa4
K 0x25 0xa5
L 0x26 0xa6
; 0x27 0xa7
' 0x28 0xa8
` 0x29 0xa9
Left Shift 0x2a 0xaa
\ 0x2b 0xab
Z 0x2c 0xac
X 0x2d 0xad
C 0x2e 0xae
V 0x2f 0xaf
B 0x30 0xb0
N 0x31 0xb1
M 0x32 0xb2
, 0x33 0xb3
. 0x34 0xb4
/ 0x35 0xb5
Right Shift 0x36 0xb6
*(小键盘) 0x37 0xb7
Left Alt 0x38 0xb8
Space 0x39 0xb9
CapsLock 0x3a 0xba

从上表可以看出:

  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用于测试键盘驱动。

标签:15,操作系统,中断,Shift,扫描,键盘,CapsLock
From: https://www.cnblogs.com/yingyulou/p/17825528.html

相关文章

  • 一个操作系统的设计与实现——第17章 系统交互
    操作系统最终是供用户使用的,所以其需要具备与用户交互的能力,交互方式可以是命令行,图形界面,甚至是触摸屏,语音,实体按钮等。本章将要实现的是系统交互。17.1外壳程序我们的操作系统使用的是基于命令行的交互模式。实现此功能的模块被称为外壳(Shell)程序。事实上,上一章的Test.c已经......
  • 一个操作系统的设计与实现——第3章 保护模式
    实模式下,内存的访问是没有任何限制的,任何程序都能访问和修改任何内存地址,这就导致了实模式下的程序,甚至操作系统自己,都可能自身难保。于是,自8086的下一代产品80286起,保护模式诞生了;进一步的,自80386起,32位保护模式诞生了。3.1内存为什么要分段在学习保护模式之前,需要先讨论一个......
  • 一个操作系统的设计与实现——第2章 主引导记录
    2.1BIOS当按下开机键的那一刻,发生了什么呢?这是一个百废待兴的时刻,所有的硬件设备都刚启动,并没有做好准备,甚至连CPU自己都是。此时,就需要一些外力帮助CPU工作起来。具体来说,在CPU刚启动时,其CS:IP被硬件电路设定为f000:fff0。这个地址并非指向内存,而是指向主板的一个非易失性ROM,......
  • 一个操作系统的设计与实现——第1章 什么是操作系统
    1.1引言什么是操作系统呢?有些读者可能会像曾经的笔者一样,认为操作系统是"一种图形界面";在学习了Linux操作系统后,认为操作系统也可以是"一种命令行"。而不同种类,不同版本的操作系统,则是"不同的图形界面",或是"不同的命令行语法"。那么,以Linux操作系统为例,读者是否想过这些问题呢......
  • 一个操作系统的设计与实现——第5章 加载内核
    一直以来,我们都在使用汇编语言对MBR编程,但对于操作系统这样的复杂程序来说,使用汇编语言是比较困难的。本章将实现操作系统内核的加载与进入。5.1读硬盘的实现原理操作系统存储于硬盘中,现在需要将其读出至内存。想要读硬盘,就需要依次进行以下操作:设定读取的扇区数设定起始扇......
  • 一个操作系统的设计与实现——第9章 硬盘驱动
    操作系统应当具备读写硬盘的能力。因此,本章将要实现的是硬盘驱动。硬盘驱动由两个函数构成:读硬盘函数与写硬盘函数。9.1读硬盘想要读硬盘,就需要提供以下三个信息:起始扇区号读取的扇区数数据存储的地址需要注意的是:读取的扇区数只能是一个8字节的整数。由于读硬盘需要使......
  • 一个操作系统的设计与实现——第8章 内存管理系统
    计算机上的任何程序,包括操作系统自己,都需要使用内存。因此,操作系统需要实现内存管理系统,以进行内存的分配和回收。在我们的操作系统中,内存管理系统由两部分组成:页分配器与页回收器。本章将实现这两个部分。8.1从虚拟地址到物理地址回顾CPU对内存地址的转换过程:使用段寄存器......
  • 一个操作系统的设计与实现——第7章 中断
    7.1什么是中断中断是一种能够随时打断CPU正常工作的机制。这句话看着挺别扭的,CPU工作的好好的,为什么要"随时打断"它?这是因为,CPU需要为诸多外部设备提供服务,以键盘为例,当键盘上的键被按下时,CPU需要对此做出响应和处理,如果不能及时响应,我们会说:"电脑很卡";如果一直都不能响应,我们会......
  • 一个操作系统的设计与实现——第6章 显卡驱动
    进入内核以后,应该做些什么呢?本章将实现一个最容易看到效果的模块:显卡驱动。6.1什么是驱动驱动这个词听起来很高大上,但实际上很简单,就是硬件的接口函数。在软件工程中,可以使用接口封装和简化设计,硬件也是一样。例如:想要读硬盘,需要很多指令设定好几个端口,然后等待硬盘就绪,最后才......
  • 一个操作系统的设计与实现——第12章 任务(三):3特权级任务
    特权级是保护模式的核心概念之一,但我们的操作系统一直没有引入这个概念。这是因为,特权级只有在3特权级任务存在时才有意义。本章将要实现的是3特权级任务的加载与任务切换。12.1特权级12.1.1特权级的功能特权级(PrivilegeLevel),是保护模式中用于限制任务权限的机制。特权级有4......