系列目录
- 序篇
- 准备工作
- BIOS 启动到实模式
- GDT 与保护模式
- 虚拟内存初探
- 加载并进入 kernel
- 显示与打印
- 全局描述符表 GDT
- 中断处理
- 虚拟内存完善
- 实现堆和 malloc
- 第一个内核线程
- 多线程运行与切换
- 锁与多线程同步
- 进入用户态
- 进程的实现
- 系统调用
- 简单的文件系统
- 加载可执行程序
- 键盘驱动
- 运行 shell
键盘输入
这个 kernel 系列项目到这里已经完成了所有基本功能的搭建了,最后两篇算是拓展完善,我们将会加入键盘功能,并在此基础上实现一个 shell 命令行界面。
键盘码
键盘的详细原理和实现比较冗长无聊,这里也不想浪费时间解释,感兴趣的话你可以在网上找资料研究。本篇会尽可能地简单化处理,把很多底层细节省略掉,只关注核心实现原理。
一般来说键盘上你按一个键,然后松开,它会产生两个电信号:
- 按下键产生的那个信号叫通码(
make code
),就是接通的意思; - 松开键产生的那个信号叫断码(
break code
),就是断开的意思;
通码和断码都称为扫描码(scan code
),对于键盘来说是一视同仁的,就是发送一个信号给主机而已,操作系统接收到这一系列信号之后,则需要将它们翻译成对应的输出字符。一个键有通、断两个码是必要的,比如在用户界面上你可以决定是按下键就打印出字符,还是一定要按下并松开才打印字符,这在用户感受上是不一样的;再例如某些组合键,Shift + a,系统连续接收到 Shfit 的通码和 a 的通码,才会翻译成一个 A 的通码,中间不可以有 Shfit 的断码,否则表示 Shift 已经松开。
中断触发
键盘的信号是通过中断来触发的,中断号 33,因此我们首先为它注册中断 handler:
register_interrupt_handler(IRQ1_INT_NUM,
&keyboard_interrupt_handler);
在 keyboard_interrupt_handler 函数里,会从端口 0x60
读取输入的 scan code,然后将它加入到缓冲区暂存。这里我们用到了一个环形缓冲区(ring buffer
),它是一个容量有限的队列,键盘中断 handler 不断将新输入的 scan code 加入到这个缓冲区尾部,而消费者则从缓冲区头部不断地读取消费 scan code 并翻译成字符。
消费阻塞等待
scan code 缓冲队列消费者是函数 read_keyboard_char_impl,它的核心逻辑在函数 process_scancode,它的功能是将读入的 scan code 翻译为字符。不过它的实现细节不必深究,十分枯燥冗长,就是对着 scan code 码表翻译而已。
int32 read_keyboard_char_impl() {
if (queue.size == 0) {
return -1;
}
int32 augchar = process_scancode((int)dequeue());
while (!(KH_HASDATA(augchar) && KH_ISMAKE(augchar))) {
if (queue.size == 0) {
return -1;
}
augchar = process_scancode((int)dequeue());
}
return KH_GETCHAR(augchar);
}
如果缓冲区是空的,或者当前的 scan code 不足以翻译成一个有效的字符(例如只读到一个 Shfit 的通码),那么它不会返回有效字符。注意它的退出判断条件:
(KH_HASDATA(augchar) && KH_ISMAKE(augchar))
即翻译出来的是一个有效字符并且是一个通码,就视作是一个合法的按键输入,需要返回给上层做反馈。
read_char 系统调用
我们从顶向下来看用户如何获取键盘输入的字符。键盘输入的处理是在 kernel 里的,作为 user 层,需要使用系统调用来获取键盘输入。我们定义一个新的系统调用 read_char,具体实现是 read_keyboard_char 函数,它调用的正是上面的 read_keyboard_char_impl
函数。如果当前无有效字符能被翻译出来,它会阻塞当前线程,那么用户端看来就是程序会卡在这里,等待键盘输入。
如果有新的键盘中断进来,表示有新 scan code,那么 kernel 会唤醒阻塞在 read_keyboard_char
里的等待线程,让它继续消费 scan code 的缓冲区队列,尝试继续翻译有效字符出来。
可以用下面的测试程序,你会在屏幕上得到一个类似 shell 命令行,或者文本编辑器里的按键反馈输出字符的效果:
void test_read_char() {
while (1) {
int8 c = read_char();
printf("%c", c);
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。