从零开始写 OS 内核 - 键盘驱动

系列目录

键盘输入

这个 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);
    }
}

naive programmer

558 声望
134 粉丝
0 条评论
推荐阅读
大数定律
大数定律的直观表达非常符合我们的直觉,例如一个普通硬币如果扔足够多次,那么正反面的次数将会无限接近于 50%;或者一个被做了弊的硬币,扔出正面的理论概率是 0.7,那么当我们扔足够多次时,正反面的次数将无...

navi1阅读 919

Redis 发布订阅模式:原理拆解并实现一个消息队列
“65 哥,如果你交了个漂亮小姐姐做女朋友,你会通过什么方式将这个消息广而告之给你的微信好友?““那不得拍点女朋友的美照 + 亲密照弄一个九宫格图文消息在朋友圈发布大肆宣传,暴击单身狗。”像这种 65 哥通过朋...

码哥字节6阅读 1.4k

封面图
C 程序眼中的 Unicode
去年写了一篇文章「在 C 程序中处理 UTF-8 字符串」,介绍了如何使用 GLib 提供的 UTF-8 字符串处理函数来实现基本的 UTF-8 文本处理。不过,GLib 是一个功能比较全面的 C 程序库,C 字符串处理仅仅是它的一个很...

garfileo3阅读 5.7k评论 5

计算机如何表示整数
在计算机中,任何的数据都是用二进制: 0 和 1 来表示。整数也不例外。生活中的 10,在 8 个字节的整数中表示为 00001010。但是这样子只能表示正数和零。怎么表示负数呢?于是有了符号位的概念。在 8 个字节的整...

kang2阅读 3k评论 7

麒麟操作系统 (kylinos) 从入门到精通 - 研发环境 - 第二十一篇 C++/C语言开发环境搭建
类别:笔记本型号:中国长城 NF14C硬件平台:飞腾处理器(ArmV8 指令集)系统:银河麒麟操作系统 V10 SP1(2203) 关键词:信创,麒麟系统,linux,c++,c,内核飞腾,arm

码上世界1阅读 2.4k评论 1

封面图
浙大版《C语言程序设计》第四版(何钦铭颜晖) 第5章 函数 课后习题答案
你也可以上程序咖([链接]),打开大学幕题板块,不但有答案,讲解,还可以在线答题。一、选择题1.在 C 语言程序中,若对函数类型未加显式说明,则函数的隐含类型为( )。A. voidB. double C. charD. int答:D解析...

茹茹1阅读 1k

(持续更新,已更新至2022年11月26日)C语言经典题集合
(持续更新,最新时间2022年11月26日)1. 三个数由小到大排序输入任意3个整数,编程实现对这3个整数进行由小到大排序井将排序后的结果显示在屏幕上 {代码...} 2. a²+b²要求输入整数a和 b, 若a²+b²的结果大与100, ...

瞿小凯阅读 1.4k评论 2

封面图

naive programmer

558 声望
134 粉丝
宣传栏