键盘,萌宝相信大家都不陌生,天天与它打交道。
但熟归熟,清楚键盘背后的原理吗?
键盘上都标有各键的名称,表明了各键所代表的意义,但是计算机是如何知道的?
组合键是怎样实现的?
按下一个代表字符的键,怎么变成平常使用的ASCII码的?
01—— 相关介绍
键盘编码器
键盘编码器(i8048),是键盘里的芯片,主要用来监控是否有键按下,弹起,然后向键盘控制器报告此键的相关信息。
键盘扫描码
一个键有按下就会有弹起,所以每个键会有两个状态,即每个键将会对应两个扫描码,键被按下时的编码叫做通码(makecode),弹起时的编码叫做断码(breakcode)。
键盘控制器
键盘控制器(i8042),不在键盘内部,被集成在南桥芯片上。
它主要是接收键盘编码器发来的扫描码(第二套),解码(转成第一套)后保存到自己的寄存器中,然后通过中断控制器发送中断请求。
i8042有4个寄存器,如下所示:
注:输入输出要视对象决定,对键盘控制器来说是输出,那么对CPU来说则是输入,使用 in 指令。
02——键盘中断流程
其实上述的相关介绍已经涉及了部分键盘中断流程,在此从头至尾具体说说,先看流程图:
03——键盘中断服务程序
键盘中断在所有的可屏蔽中断中优先级仅次于时钟中断,也需要尽快的处理。
在Linux 0.11里的整个键盘服务程序都是用汇编来写的,汇编语言直接操作底层的指令,没有编译器来增加额外的东西,所以运行起来比高级语言写的程序快,但也增加了编写程序的难度。
linux0.11版本的键盘中断服务程序的框架源码如下图所示:
这个框架程序主要做了以下事情:
保护现场——压栈
上文中写到压栈ss, esp, eflags, cs, eip, error_code (若有特权级变化且中断带有错误码) 来保存现场,那只是CPU自动执行的部分,完全保存原任务的信息还是在中断处理程序中进行的。
如上图所示,键盘中断服务程序里通用寄存器只保存了4个,eax, ebx, ecx, edx,若为了省事不追求效率完全可以无脑操作pushad压榨所有的通用寄存器。
读取扫描码
inb $0x60, al 从键盘控制器的输出缓存区0x60端口读取扫描码。
若不从输出缓冲区读取数据的话,键盘控制器是不会继续工作的,意思是无论你怎么按键,键盘控制器不会响应键盘操作,不会存下新的扫描码发送中断信号等。
当然不读取扫描码后续的键盘中断程序也没法工作没有意义。
判断是否为 0xe0 或 0xe1
如果扫描码是 0xe0 或者 0xe1,那说明这个键的扫描码是有多个字节的,需要先保存下来等待接下来的扫描码组合成完整的扫描码。
寻址、调用相应的键处理程序
拿到完整的扫描码之后就该去寻找相应的键处理程序了
源码中有个key_table,table, 说明它是一张表,或者说一个数组
这里面就按照扫描码大小存放了各个键的实际处理程序地址。
如何找到相应的键处理程序呢?
其实跟数组用下表获取元素一样
据源码所示采用比例变址寻址的方式,即key_table(, %eax, 4)
也就是说相应的键处理程序的地址是key_table + eax * 4。key_table
相当于数组首地址;
eax里面存放的扫描码,扫描码可以看成数字索引号
相当于数组下标;
地址32位,4字节,所以乘4
回复现场—出线
压栈保护现场的逆过程,在此不再赘述
萌宝要提醒各位,需要注意执行到 iret 时的栈顶应是 eip哦~