KEYBOARD

键盘,咱们做计算机这一行的自然不必多说,天天与它打交道。但熟归熟,清楚键盘背后的原理吗?键盘上都标有各键的名称,表明了各键所代表的意义,但是计算机是如何知道的?组合键是怎样实现的?按下一个代表字符的键,怎么变成平常使用的ASCII码的?

看完本文,相信你就能了解键盘的本质,知晓这些问题答案。

01

相关介绍

键盘编码器

键盘编码器(i8048),是键盘里的芯片,主要用来监控是否有键按下,弹起,然后向键盘控制器报告此键的相关信息。键盘编码器就像是键盘的嘴,让键盘能够说话,表达目前按键状态。Num Lock键和Caps Lock键的LED灯的开关也归它控制。

键盘扫描

上述所说的信息就是键盘扫描码,一个键有按下就会有弹起,所以每个键会有两个状态,即每个键将会对应两个扫描码,键被按下时的编码叫做通码(makecode),弹起时的编码叫做断码(breakcode)。

大部分键的通码和断码都是 8 位 1 字节,但有些操作控制键如 ctrl、alt,附加键如Insert,小键盘区如 /,方向键等是 2 字节甚至多个字节。有多个字节的扫描码都是以 0xe0 开头。只有Pause Break一个键是以 0xe1 开头。

断码与通码的关系:断码 = 通码 + 0x80。0x80 二进制表示为 1000 0000,所以对于断码和通码可以这样理解,它们由8位比特组成,最高位第7位表示按键状态,1表示按下,0表示弹起。

注:上述为第一套键盘扫描码的情况,现下使用的键盘基本使用的第二套键盘扫描码,但是为了兼容,最终还是要将第二套扫描码转化为第一套扫描码,这也是键盘控制器工作的一部分。

键盘控制器

键盘控制器(i8042),不在键盘内部,被集成在南桥芯片上。它主要是接收键盘编码器发来的扫描码(第二套),解码(转成第一套)后保存到自己的寄存器中,然后通过中断控制器发送中断请求。

i8042有4个寄存器,如下所示:

键盘insert键在哪个位置_hashmap底层原理面试_mybatis底层原理

其中输入缓冲区和输出缓冲区共用0x60端口,状态控制器和控制寄存器共用一个0x64端口。

共用不会冲突吗?注意读写状态的不同,CPU使用int指令从8042读数据时 0x60 代表输出缓冲区,CPU使用out指令将数据写入8042时 0x60代表输入缓冲区,状态寄存器和控制寄存器同理。

注:输入输出要视对象决定,对键盘控制器来说是输出,那么对CPU来说则是输入,使用 in 指令。

每个寄存器都是8位的键盘insert键在哪个位置,保存扫描码时最多只能保存8位1字节的扫描码,每次键盘中断服务程序也只能处理 1 字节的扫描码。也就是说键盘中断的次数不是你按键、弹起的次数,而是按键、弹起对应的通码、断码(第一套)字节数。由此可以看出平时我们敲键盘时那是发生了无数次的中断呐。

那有的按键信息不是多个字节的扫描码吗? 的确是,但硬件环境如此,不能改变,只能在软件上下功夫,这就是接下来要说的键盘的中断程序,先看看键盘中断的流程,好有个清晰的路线。

02

键盘中断流程

其实上述的相关介绍已经涉及了部分键盘中断流程,在此从头至尾具体说说,先看流程图:

键盘insert键在哪个位置_mybatis底层原理_hashmap底层原理面试

1. 键盘编码器监控是否有键按下或弹起,若有键按下,向键盘控制器发送此键的通码;若有键弹起,则发送断码(基本发送第二套键盘扫描码)。

2. 键盘控制器接收来自键盘编码器发来的扫描码,解码转化成第一套扫描码,保存到自己的输出缓冲区中,然后通过中断控制器向CPU发送键盘中断信号。

3. 后面的流程基本和上文讲的中断流程一样了,在此简述:未关中断的情况下CPU响应,中断控制器再通过数据线发送中断向量号,CPU据向量号定位中断服务程序,期间检查特权级自动压栈,然后运行中断服务程序处理中断。

03

键盘中断服务程序

键盘中断在所有的可屏蔽中断中优先级仅次于时钟中断,也需要尽快的处理。在Linux 0.11里的整个键盘服务程序都是用汇编来写的,汇编语言直接操作底层的指令,没有编译器来增加额外的东西,所以运行起来比高级语言写的程序快,但也增加了编写程序的难度。

linux0.11版本的键盘中断服务程序的框架源码如下图所示:

这个框架程序主要做了以下事情:

保护现场——压栈

上文中写到压栈ss, esp, eflags, cs, eip, error_code (若有特权级变化且中断带有错误码) 来保存现场,那只是CPU自动执行的部分,完全保存原任务的信息还是在中断处理程序中进行的。

如上图所示,键盘中断服务程序里通用寄存器只保存了4个,eax, ebx, ecx, edx,若为了省事不追求效率完全可以无脑操作pushad压栈所有的通用寄存器,但人家是Linux系统嘛,虽然只是0.11版本,但也要追求精确,效率,只压栈中断程序需要用到,可能破坏的寄存器。

读取扫描码

inb $0x60, al从键盘控制器的输出缓存区0x60端口读取扫描码。若不从输出缓冲区读取数据的话,键盘控制器是不会继续工作的,意思是无论你怎么按键,键盘控制器不会响应键盘操作,不会存下新的扫描码发送中断信号等。当然不读取扫描码后续的键盘中断程序也没法工作没有意义,在此只是说明一下。

判断是否为 0xe0 或 0xe1

如果扫描码是 0xe0 或者 0xe1,那说明这个键的扫描码是有多个字节的键盘insert键在哪个位置,需要先保存下来等待接下来的扫描码组合成完整的扫描码。

寻址、调用相应的键处理程序

拿到完整的扫描码之后就该去寻找相应的键处理程序了,源码中有个key_table,table, 说明它是一张表,或者说一个数组,这里面就按照扫描码大小存放了各个键的实际处理程序地址。

如何找到相应的键处理程序呢?其实跟数组用下表获取元素一样,只是汇编里面有一些听起来高大上的名词:据源码所示采用比例变址寻址的方式,即key_table(, %eax, 4),也就是说相应的键处理程序的地址是key_table + eax * 4。key_table,相当于数组首地址;eax里面存放的扫描码,扫描码可以看成数字索引号,相当于数组下标;地址32位,4字节,所以乘4。

限 时 特 惠: 本站每日持续更新海量各大内部创业教程,一年会员只需98元,全站资源免费下载 点击查看详情
站 长 微 信: muyang-0410