MacOS环境-手写操作系统-12-键盘中断机制

简介: MacOS环境-手写操作系统-12-键盘中断机制为键盘建立中断机制

为键盘建立中断机制

1.简介

我们实现了键盘中断的响应 但响应的处理比较简单 只是向界面打印一条字符串而已


本节 当键盘上的一个按键按下时 键盘会发送一个中断信号给CPU


与此同时 键盘会在指定端口(0x60) 输出一个数值


这个数值对应按键的扫描码(make code) 当按键弹起时 键盘又给端口输出一个数值 这个数值叫断码(break code).我们以按键按键’A’为例 当按键’A’按下时 键盘给端口0x60发出的扫描码是0X1E 当按键’A’弹起时 键盘会给端口0x60发送断码0x9E


键盘的扫描码和断码列表:

2.代码

从上图可以看到


当按键 ‘A’按下时 键盘向端口发送数值0x1E 弹起时发送数值0x9e


同理按键’B’按下时,键盘向端口0x60发送数值0x30,弹起时向端口发送0xB0,


我们更改上一节的中断处理代码,使得键盘按键按下和弹起时,在界面上显示出按键的make code 和 break code:

void intHandlerFromC(char* esp) {
    char*vram = bootInfo.vgaRam;
    int xsize = bootInfo.screenX, ysize = bootInfo.screenY;
    io_out8(PIC_OCW2, 0x21);
    unsigned char data = 0;
    data = io_in8(PORT_KEYDAT);
    char* pStr = charToHexStr(data);
    static int showPos = 0;
    showString(vram, xsize, showPos, 0, COL8_FFFFFF, pStr);
    showPos += 32;
}

char   charToHexVal(char c) {
    if (c >= 10) {
        return 'A' + c - 10;
    } 

    return '0' + c;
}

char*  charToHexStr(unsigned char c) {
    int i = 0;
    char mod = c % 16;
    keyval[3] = charToHexVal(mod);
    c = c / 16;
    keyval[2] = charToHexVal(c);

    return keyval;
}

intHandlerFromC 是C语言处理中断的函数


我们注意看语句:

io_out8(PIC_OCW2, 0x21);

其中PIC_OCW2 的值是0x20 也就是主PIC芯片的控制端口


上一节我们解释过 0x21对应的是键盘的中断向量


当键盘中断被CPU执行后 下次键盘再向CPU发送信号时 CPU就不会接收


要想让CPU再次接收信号 必须向主PIC的端口再次发送键盘中断的中断向量号


PORT_KEYDAT 的值是0x60 io_in8 是内核汇编部分提供的函数 它从指定端口读入数据 并返回


charToHexStr 作用是将键盘输出的数值转换为16进制的字符串。


当一个按键被按下然后弹起时 上面的intHandlerFromC会调用两次


从而一次按键使得界面上会连续打印两个16进制数值


下面我们对代码进行编译:


编译C文件


i386-elf-gcc -m32 -fno-asynchronous-unwind-tables -s -c -o write_vga_desktop.o write_vga_desktop.c


反汇编o文件


./objconv -fnasm write_vga_desktop.o write_vga_desktop.asm


删除无用部分


修改kernel


%include "write_vga_desktop.asm"


修改boot(直接放大一点)直接读了18个扇区 肯定够用了


mov          AL,  18        ; AL 表示要练习读取几个扇区


编译boot


nasm -o boot.bat boot.asm


编译kernel


nasm -o kernel.bat kernel.asm


运行java 生成system.img


然后按下按键结果显示如下:


(出现乱的是因为我多按了 导致覆盖了)

3.优化

中断 实际上是将CPU当前正在执行的任务给打断 让CPU先处理中断任务 然后再返回处理原先的任务


这时会有一个问题 如果中断处理过久 就会对CPU原来的任务造成负面影响


就以键盘中断为例 我们处理键盘中断时 要获取按键的扫描码和断码的数值


同时将数值转换为字符串 最后再将字符串的每一个字符绘制到界面上


这一系列其实是很耗时的计算 假设这时候有网络数据抵达系统 但是CPU忙于处理键盘中断 不能及时接收网络数据 便会丢失网络数据


所以 对于中断 我们要尽可能快的处理 然后把控制器交还给原来的任务


对于键盘中断 我们可以把键盘发送的扫描码和断码数值缓存起来 然后把控制器交换给原来任务 等到CPU稍微空闲时再处理键盘事件


因此我们为键盘中断设置一个缓冲区:

struct KEYBUF {
    unsigned char key_buf[32];
    int next_r, next_w, len;
};

struct KEYBUF keybuf;

我们设置了长为32字节的缓冲区 当键盘中断接收到数据时 从next_w指向的位置开始写入 len用来表示当前缓冲区中的有效数据长度


例如 当我们按下’A’和’B’两个按键时 我们会向缓冲区写入4个字节 于是len就等于4


void intHandlerFromC(char* esp) {
    char*vram = bootInfo.vgaRam;
    int xsize = bootInfo.screenX, ysize = bootInfo.screenY;
    io_out8(PIC_OCW2, 0x21);
    unsigned char data = 0;
    data = io_in8(PORT_KEYDAT);
    if (keybuf.len < 32) {
       keybuf.key_buf[keybuf.next_w] = data;
       keybuf.len++;
       keybuf.next_w = (keybuf.next_w+1) % 32;
    }
}


每次键盘中断 代码都将相应的扫描码和断码写入缓冲区


如果缓冲区写满后 也就是next_w的值达到32 那么通过一次求余 next_w会重新设置为0


也就是说一旦缓冲区写满后 下次写入将从头开始


键盘数据的输出转移到内核的主函数CMain中:

void CMain(void) {
   ...

   int data = 0;
    for(;;) {
       io_cli();
       if (keybuf.len == 0) {
           io_stihlt();
       } else {
           data = keybuf.key_buf[keybuf.next_r];
           keybuf.next_r = (keybuf.next_r + 1) % 32;
           io_sti();

           char* pStr = charToHexStr(data);
           static int showPos = 0;
           showString(vram, xsize, showPos, 0, COL8_FFFFFF, pStr);
           showPos += 32;           
       }
    }
}

主函数不再单纯的死循环 而是每次循环的时候查看键盘缓冲区是否有数据 有数据的话就把缓冲区中的数据显示到屏幕上 同时next_r增加 以指向下一个要输出的数据


4.编译运行

编译C文件


i386-elf-gcc -m32 -fno-asynchronous-unwind-tables -s -c -o write_vga_desktop_keyboard_buf.o write_vga_desktop_keyboard_buf.c

反汇编o文件


./objconv -fnasm write_vga_desktop_keyboard_buf.o write_vga_desktop_keyboard_buf.asm


删除无用部分


修改kernel


%include "write_vga_desktop_keyboard_buf.asm"


修改boot(直接放大一点)直接读了18个扇区 肯定够用了


mov          AL,  18        ; AL 表示要练习读取几个扇区


编译boot


nasm -o boot.bat boot.asm


编译kernel


nasm -o kernel.bat kernel.asm


运行java 生成system.img


最后在编译的时候 遇到了 kernel 中报错(提示找不到 keybuf)


原博客老师写的是:


struct KEYBUF keybuf;


我改成了


static struct KEYBUF keybuf;


在C中 改成了这样 就可以了(也是个坑呀!)


运行后看不出区别来 所以我就不放图了


目录
相关文章
|
21天前
|
机器学习/深度学习 人工智能 物联网
操作系统的心脏——深入理解内核机制
在本文中,我们揭开操作系统内核的神秘面纱,探索其作为计算机系统核心的重要性。通过详细分析内核的基本功能、类型以及它如何管理硬件资源和软件进程,我们将了解内核是如何成为现代计算不可或缺的基础。此外,我们还会探讨内核设计的挑战和未来趋势,为读者提供一个全面的内核知识框架。
|
22天前
|
消息中间件 安全 Linux
深入探索Linux操作系统的内核机制
本文旨在为读者提供一个关于Linux操作系统内核机制的全面解析。通过探讨Linux内核的设计哲学、核心组件、以及其如何高效地管理硬件资源和系统操作,本文揭示了Linux之所以成为众多开发者和组织首选操作系统的原因。不同于常规摘要,此处我们不涉及具体代码或技术细节,而是从宏观的角度审视Linux内核的架构和功能,为对Linux感兴趣的读者提供一个高层次的理解框架。
|
1月前
|
存储 消息中间件 算法
深入探索操作系统的心脏——内核机制解析
本文旨在揭示操作系统核心——内核的工作原理,通过剖析其关键组件与机制,为读者提供一个清晰的内核结构图景。不同于常规摘要的概述性内容,本文摘要将直接聚焦于内核的核心概念、主要功能以及其在系统管理中扮演的角色,旨在激发读者对操作系统深层次运作原理的兴趣与理解。
|
29天前
|
安全 Linux 数据安全/隐私保护
深入探索Linux操作系统的多用户管理机制
【10月更文挑战第21天】 本文将详细解析Linux操作系统中的多用户管理机制,包括用户账户的创建与管理、权限控制以及用户组的概念和应用。通过具体实例和命令操作,帮助读者理解并掌握Linux在多用户环境下如何实现有效的资源分配和安全管理。
|
2月前
|
监控 Linux 云计算
Linux操作系统在云计算环境中的实践与优化###
【10月更文挑战第16天】 本文探讨了Linux操作系统在云计算环境中的应用实践,重点分析了其在稳定性、安全性和高效性方面的优势。通过具体案例,阐述了Linux如何支持虚拟化技术、实现资源高效分配以及与其他开源技术的无缝集成。文章还提供了针对Linux系统在云计算中的优化建议,包括内核参数调整、文件系统选择和性能监控工具的应用,旨在帮助读者更好地理解和应用Linux于云计算场景。 ###
55 3
|
2月前
|
存储 资源调度 算法
操作系统的心脏:深入理解内核架构与机制####
【10月更文挑战第16天】 本文旨在揭开操作系统最神秘的面纱——内核,通过剖析其架构设计与关键机制,引领读者一窥究竟。在这篇探索之旅中,我们将深入浅出地讨论内核的基本构成、进程管理的智慧、内存分配的策略,以及那至关重要的系统调用接口,揭示它们是如何协同工作,支撑起现代计算机系统的高效运行。这既是一次技术的深潜,也是对“看不见的手”调控数字世界的深刻理解。 ####
54 3
|
1月前
|
缓存 调度
操作系统的心脏:深入理解内核机制
【10月更文挑战第26天】 在数字化时代,操作系统是计算机系统不可或缺的核心。本文旨在揭示操作系统内核的神秘面纱,探讨其工作原理和重要性。通过深入浅出的语言,我们将一窥究竟,了解内核如何协调硬件与软件,确保计算机系统的稳定运行。
|
2月前
|
存储 C语言 iOS开发
MacOS环境-手写操作系统-48-让内核从错误中恢复
MacOS环境-手写操作系统-48-让内核从错误中恢复
45 0
|
2月前
|
存储 API C语言
MacOS环境-手写操作系统-46,47-C语言开发应用程序
MacOS环境-手写操作系统-46,47-C语言开发应用程序
40 0
|
2月前
|
编译器 API C语言
MacOS环境-手写操作系统-45-C语言开发应用程序
MacOS环境-手写操作系统-45-C语言开发应用程序
55 0