解析Linux键盘组合键产生信号的完整过程:从硬件中断到信号发送

前言

每一个了解Linux的都知道这样一个知识,Ctrl+C组合键能够终止一个进程。
在这里插入图片描述

个人了解进程相关知识之后知道,一个进程被终止只会有有三种情况:

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码运行异常,进程收到了信号,被强制终止。

然后,就去了解信号,发现存在键盘产生信号这一种方式,Ctrl+C组合键可以送2号信号SIGINT给进程,所以,上面的图片中的死循环被终止了因为进程收到了2号信号。

个人对于其中的原理非常好奇,所以经过研究就有了这一篇文章。

正文

Ctrl+CCtrl键和C键的组合,第一个问题是,计算机怎么知道有按键被按下了?

键盘上的每一个按键下方都配备了一个开关。这些开关通过刻画在电路板上的硬件电路以矩阵形式相互连接。在正常情况下,这些开关处于断路状态,这意味着没有电信号通过。然而,当按键被按下时,开关所在的电路就会连通,从而产生电信号,产生了电信号就意味着有一个按键被按下了。

第二个问题是,键盘上按键这么多,计算机怎么知道那些按键被按下了?

为了检测按键的按下和弹起,键盘内部配备了一个叫做键盘编码器(如i8048)的芯片。该芯片通过轮询的方式不断监控按键的状态,并将按键被按下或弹起而产生的相关信息向键盘控制器报告。这些信息被称为键盘扫描码,它们代表了按键的具体位置和状态。

关于键盘扫描码,现有的编码方式一共有三种,相应的也就存在三套键盘扫描码,现今的键盘大多数默认使用第二套键盘扫描码,但也不排除使用第一套和第三套的,所以为了兼容,键盘控制器会统统地将来自键盘编码器的扫描码转换为第一套扫描码。

关于第一套键盘扫描码,每个按键都有两个状态:按下和弹起,因此每个按键都会对应两个扫描码。按键按下时的编码称为通码 (makecode),而弹起时的编码称为断码 (breakcode)。大部分键的通码和断码都是用1个字节来表示的,但也有一些特殊键,如控制键(ctrlalt)、附加键(Insert)、小键盘区键(/)以及方向键等,它们的扫描码可能是2个字节甚至更多来表示的。这些多字节的扫描码通常以0xE0开头,只有Pause Break键的扫描码是以0xE1开头。

断码与通码之间存在一个简单的关系:断码 = 通码 + 0x80。从二进制表示来看,0x801000 0000,这意味着在8个比特中,最高位(第7位)表示按键的状态,0表示按下,1表示弹起。

举个例子,假设一个按键的通码是 0x45,那么它的二进制表示是 0100 0101。如果我们将它的最高位设为1,就得到断码 0xC5,对应的二进制表示是 1100 0101。这样,按键按下时发送的通码和按键释放时发送的断码之间的关系就清楚了。

这里再来说一下另一个与键盘有关的芯片——键盘控制器(i8042),键盘控制器并不位于键盘内部,而是被集成在主板的南桥芯片上。它的主要任务是接收键盘编码器发来的扫描码(第二套),解码(转换为第一套)后保存到自己的寄存器中,并通过中断控制器向CPU发送中断请求。

分别来说说这里涉及到的三个东西:南桥、键盘控制器内的寄存器、中断控制器。

南桥芯片是主板芯片组的一部分,主要负责系统的输入输出功能。在早期的计算机架构中,晶体管的数量相对偏少,处理器集成度较低,必须要由主板芯片组来承担大量功能,芯片组分为南桥芯片组和北桥芯片组两部分,北桥芯片负责CPU与内存的数据交换、图形处理以及CPU与PCIE数据交换,而南桥则负责系统的输入输出功能。然而,随着CPU制造工艺的进步和集成度的提高,许多原本由北桥芯片负责的功能现在已经被集成到CPU内部,导致北桥芯片逐渐被淘汰。现在的Intel芯片组只保留了南桥芯片,而AMD也只有早期的主板还保留了北桥和南桥。

键盘控制器内部拥有 4 个大小为 8 bits 的寄存器,它们的名称及作用分别如下:

  1. Status Register(状态寄存器)
    一个8位只读寄存器,任何时刻均可被cpu读取,各比特位定义如下:
    Bit7:奇偶校验错误。如果从键盘接收到的数据出现奇偶校验错误,此位将被置1。
    Bit6:当键盘控制器在接收数据时发生超时,此位将被置1。
    Bit5:当键盘控制器在发送数据时发生超时,此位将被置1。
    Bit4:如果为1,表示键盘未被禁用;如果为0,表示键盘被禁用。
    Bit3:如果为1,表示输入缓冲器中的内容为命令;如果为0,表示输入缓冲器中的内容为数据。
    Bit2:在加电启动时为0,在自检通过后置1。
    Bit1:如果为1,表示输入缓冲器已满,等待被 i8042 取走数据后将被清零。
    BitO:如果为1,表示输出缓冲器已满,等待 CPU 读取数据后将被清零。

  2. Control Register(控制寄存器)
    也被称作 Controller Command Byte (控制器命令字节)。各比特位定义如下:
    Bit7: 保留。应该始终为0。
    Bit6: 如果为1,键盘控制器将会将第二套扫描码翻译为第一套。
    Bit5: 如果为1,将禁止鼠标。
    Bit4: 如果为1,将禁止键盘。
    Bit3: 如果为1,将忽略状态寄存器中的 Bit4。
    Bit2: 如果为1,将设置状态寄存器中的 Bit2。
    Bit1: 如果为1,将启用鼠标中断。
    BitO: 如果为1,将启用键盘中断。

  3. Output Buffer(输出缓冲器)
    一个8位只读寄存器。键盘驱动程序从这个寄存器中读取数据。数据包括【扫描码】、【发往 i8042 命令的响应】、【间接的发往 i8048 命令的响应】等。

  4. Input Buffer(输入缓冲器)
    一个8位只写寄存器。缓冲键盘驱动程序发来的内容。内容包括【发往 i8042 的命令】、【通过 i8042 间接发往 i8048 的命令】、【以及作为命令参数的数据】等。

大致了解了这四个寄存器的作用之后,键盘控制器的工作过程就清晰了,键盘控制器接收到来自键盘编码器发送的通码或者断码,它会先把收到的这个通码或者断码解码转化成第一套扫描码,再放到输出缓冲器(Output Buffer)中,同时状态寄存器的 bit0 会被设置为1,表示键盘数据输入就绪,等待读取。然后键盘控制器回向中断控制器发送一个中断请求信号。

所以就涉及到接下来要将的第三个硬件——中断控制器。

中断控制器是计算机系统中的另一个硬件组件,负责管理各种设备控制器(比如键盘控制器、鼠标控制器、网卡控制器)发送过来的中断请求。中断控制器中较为流行的是Intel 8259A芯片,下面以8259A工作原理的例研究一下中断控制器干了什么:

  1. 8259A向CPU发送中断请求

8259A收到来自键盘控制器的中断请求,它会先检测内部的一个叫做 IMR 的寄存器(英文名称:Interrupt Mask Register,中文翻译:中断屏蔽寄存器),查看一下收到的这个键盘中断信号是否被屏蔽,如果没有被屏蔽,8259A就会向CPU的某个核心发送中断请求。

  1. CPU保存线程上下文数据。

大多数情况下,CPU的这个核心正在执行某一个线程的某一条指令(调度线程代码),由于中断请求的优先级比调度当前线程的优先级要高,所以CPU会停下当前线程的调度工作,转而进行中断处理,但是中断处理完之后,如果该线程的时间片没有过期,CPU就又得继续调度这个被暂停的线程,这个需求就又产生另一个问题,CPU必须知道这个线程被暂停之前执行到那一条指令了,执行这条指令需要的数据是什么,存储在CPU哪个寄存器内;

要知道,CPU内部的寄存器只有一套,如果中断处理过程中恰好用到了某个寄存器,这个寄存器恰好保存了被暂停线程的数据,这就会导致数据因为覆盖而丢失,所以CPU暂停线程后,还要将其在寄存器产生的所有信息保存操作系统内核中,这个过程就叫做“保存线程上下文”。

  1. CPU响应中断,并向8259A获取中断向量号。

CPU保存线程上下文之后,向8259A发送一个中断回复信号,8259A收到回复信号后,会在内部做一些处理,表示该中断正在被CPU处理,然后,CPU再次向8259A发送一个信号,表示想要获取中断向量号,8259A通过数据总线向CPU发送中断向量号,至此8259A或者说中断控制器的工作大致就完了,CPU会将中断向量号存储在某个寄存器中。

以上就是键盘产生信号的硬件部分的工作,往下就是软件部分的工作了,主要包括中断向量号,中断向量表,键盘驱动程序,键盘文件缓冲区和 shell 程序的工作了。

首先是中断向量号与中断向量表:

在操作系统内核中存在这样一张表,英文名称,Interrupt Vector Table,翻译过来就叫做中断向量表,这张表里存储的是中断源所对应的中断处理程序的入口地址, CPU会根据中断向量号在中断向量表中索引到处理该中断的中断处理程序的入口地址,通过地址找到中断处理程序然后执行该程序,这就是中断处理的大致流程。

翻译成人话就是,所谓中断向量表其实就是一个函数指针数组,里面存储的地址各种硬件设备驱动程序所提供的能够操作自身硬件的函数(关于驱动后面会谈到),这就是中断向量表的真面目,至于中断向量号,中断向量表不是个数组吗,数组不是有下标吗,所以中断向量号这个看起来高大上的东西说白了就是数组的下标。

其次是键盘驱动程序:

讲到键盘驱动程序就又得重谈一下硬件的设备控制器。我们都知道硬件与硬件之间的运作原理和机制之间的差别是非常大的,像键盘,硬盘,网卡这些就不是能够混为一谈的东西,为了屏蔽设备之间的差异,每个设备都有一个叫设备控制器(Device Control) 的组件,比如硬盘有硬盘控制器、显示器有视频控制器,键盘有键盘控制器等。

但是只有设备控制器还是不够,虽然设备控制器屏蔽了设备的众多细节,但每种设备的控制器的寄存器、缓冲区等使用模式都是不同的,所以为了屏蔽「设备控制器」的差异,引入了设备驱动程序。

设备控制器不属于操作系统范畴,它是属于硬件,而设备驱动程序属于操作系统的一部分,操作系统的内核代码可以像本地调用代码一样使用设备驱动程序的接口,而设备驱动程序是面向设备控制器的代码,它发出操控设备控制器的指令后,才可以操作设备控制器,不同的设备控制器虽然功能不同,但是设备驱动程序会提供统一的接口给操作系统,这样不同的设备驱动程序,就可以以相同的方式接入操作系统。

至此,内核通过驱动控制设备控制器,最后达成对硬件设备的统一管理,然后这也就是解释了,在了解键盘控制器(i8042)时,读取输出缓冲器的对象是键盘驱动程序。

驱动程序处理扫描码

再来梳理一下,键盘控制器中的数据已经准备好了,差的是通过通知键盘驱动程序来读取,而通知的手段就是硬件中断机制,接下来就是要理解键盘驱动程序如何读取或者说,读取到扫描符之后要做什么:

键盘驱动程序从键盘控制器的输出缓冲器读取一个字节大小的扫描码时,它首先会检查这个扫描码是否表示一个按键的按下(通码)或释放(断码)。接着,驱动程序会进行一系列的转换和检测:

首先,为了确保按键的稳定性和准确性,驱动程序会检查是否是真正的按键事件,而不是由于电气噪声或按键抖动引起的误报。这通常涉及到检测按键的多次按下和释放,并在一定时间内稳定后才认为是一个有效的按键事件。

然后,驱动程序会根据扫描码来判断它是普通字符数据(如字母、数字、标点符号等)还是属于命令(如Ctrl+CCtrl+D、Shift、方向键等)。

如果驱动程序判断当前读取的是普通字符数据,它会做这样的一件事:
键盘驱动程序会根据当前键盘的布局和状态(如大写锁定、数字锁定等)来确定具体的字符,并将其转换为对应的ASCII码或Unicode码,然后把它写入键盘的内核级文件缓冲区中。问题来了,键盘是硬件,怎么跟文件扯上关系了?这就不得不谈到Linux系统“一切皆文件”的设计理念了。

虽然键盘是硬件,但是在系统眼里,它被当成一个文件来处理,读取键盘数据在操作系统的运作逻辑里被当成是读取键盘文件的内容,既然键盘是文件,操作系统就会在内核中为其创建相应的 struct file 结构体对象,同时还有一段用于存储文件数据的内核级文件缓冲区,这个缓冲区是内核空间的一部分,用于在用户和内核之间传递数据。

当用户空间程序(如shell、文本编辑器等)需要读取键盘输入时,它们会打开相应的设备文件,并通过调用操作系统提供的系统调用从内核级文件缓冲区中读取数据。这些数据可能是字符、按键事件或其他类型的信息,具体取决于用户空间程序的需求。

如果驱动程序判断当前读取的命令,它会做这样的一件事:
驱动程序可能会产生特定的系统事件或调用特定的系统服务,而不是直接生成字符。

举个例子,我们都知道Ctrl+C组合键通常用于发送中断信号(SIGINT),用于终止当前正在运行的进程。假设按下的是Ctrl+C组合键,键盘驱动程序不会直接进行任何操作,它会通知操作系统内核,“通知”只是说法,其实也是一种中断机制,不过这是软件层面的中断,所以,操作系统内核接收到这个通知后,会调用相应的中断处理程序来处理这个中断事件。在中断服务例程中,内核会将Ctrl+C事件转换为相应的信号,通常是中断信号(如SIGINT)。内核接着会将这个信号发送给前台进程组中的所有进程,在大多数情况下,前台进程组只包含一个进程,即你当前在shell中直接运行的进程。

进程收到了信号之后,就会如果处理信号的动作没有被修改过的话,就会执行默认动作,SIGINT 信号的默认动作就是终止。

至此,键盘组合键产生信号的从硬件层面到软件层面的完整过程解析完毕。

参考文章

  1. 在键盘上敲了一个字母之后,电脑内部发生了什么?
  2. 《键盘敲入 A 字母时,操作系统期间发生了什么…》
  3. 《Intel 8042键盘控制器详细介绍》
  4. 《键盘是如何工作的?》
  5. 《从按键到响应,键盘的底层原理是什么?》
  6. 《主板上的南桥和北桥是什么意思?》
  7. 《什么是中断向量表》
  8. 《一文讲透计算机的“中断”》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/7680.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

鸿蒙OpenHarmony【基于Hi3516DV300开发板(时钟应用开发)】

概述 本文将介绍如何快速搭建基于OpenHarmony标准系统(Hi3516DV300开发板)的应用开发环境,并基于一个时钟APP示例逐步展示应用的创建、开发、调试和安装等流程。示例代码可以通过本链接获取。 时钟App是一款显示实时时间的应用,…

【GDPU】数据结构实验十 哈夫曼编码

【实验内容】 1、假设用于通信的电文仅由8个字母 {a, b, c, d, e, f, g, h} 构成,它们在电文中出现的概率分别为{ 0.07, 0.19, 0.02, 0.06, 0.32, 0.03, 0.21, 0.10 },试为这8个字母设计哈夫曼编码。 提示:包含两个过程:(1)构建…

【iOS】——浅析CALayer

文章目录 一、CALayer介绍二、UIview与CALayer1.区别2.联系 三、CALayer的使用1.初始化方法2.常用属性 四.CALayer坐标系1.position属性和anchorPoint属性2.position和anchorPoint的关系3.position、anchorPoint和frame的关系 五、CALayerDelegate六、CALayer绘图机制1.绘图流程…

利用Jenkins完成Android项目打包

问题和思路 目前存在的问题 打包操作由开发人员完成,这样开发进度容易被打断。 解决问题的思路 将打包操作交测试/产品/开发人员来完成,主要是测试/开发。 按照以上的思路,那么JenkinsGradle的解决方案是比较经济的,实现起来…

鸿蒙内核源码分析(互斥锁篇) | 互斥锁比自旋锁丰满多了

内核中哪些地方会用到互斥锁?看图: 图中是内核有关模块对互斥锁初始化,有文件,有内存,用消息队列等等,使用面非常的广.其实在给内核源码加注的过程中,会看到大量的自旋锁和互斥锁,它们的存在有序的保证了内核和应用程序的正常运行.是非常基础和重要的功能. 概述 自旋锁 和…

5.7 线程

进程:解耦稳定,内容之间是不相关的,通信不便利,理论上进程的软硬件的切换时间以及创建开销非常大。--------》资源共享线程实现 线程的问题:本质就是不解耦,一个出问题别的就很有可能出问题,同…

Scanner中next()、nextInt()、nextLine()、hasNext()、hasNextInt()的使用方法及注意事项

目录 1、next()、nextInt()、nextLine()的使用方法及区分 2、循环时如何使用hasNext方法 3、用hasNextInt()作为判断下一个输入是否为数字需要配合next()方法使用 1、next()、nextInt()、nextLine()的使用方法及区分 三者简单定义 next():此方法遇见第一个有效字符…

使用AIGC生成软件类图表

文章目录 如何使用 AI 生成软件类图表什么是 MermaidMermaid 的图片如何保存?mermaid.liveDraw.io Mermaid可以画什么图?流程图时序图 / 序列图类图状态图甘特图实体关系图 / ER图 如何使用 AI 生成软件类图表 ChatGPT 大语言模型不能直接生成各类图表。…

linux系统下产生Segmentation fault 与 Segmentation fault (core dumped)!!!

最近在学习的过程中,遇到了Segment fault(段错误)的问题,经过一番查找资料,学到了一些相关知识,这里做一个梳理,以防以后在遇到类似的问题,并且希望能够帮助到大家一丝丝&#xff01…

python中numpy库使用

array数组 生成array数组 将list转化为array数组 import numpy as np np.array([1,2],typenp.int32)其中dtype定义的是元素类型,np.int32指32位的整形 如果直接定义dtypeint 默认的是32位整形。 zeors和ones方法 zeros()方法,该方法和ones()类似&a…

有什么方便实用的成人口语外教软件?6个软件教你快速进行口语练习

有什么方便实用的成人口语外教软件?6个软件教你快速进行口语练习 口语能力在语言学习中占据着重要的位置,因为它直接关系到我们与他人进行交流和沟通的效果。为了提高口语能力,很多成人选择通过外教软件进行口语练习,这些软件提供…

GNU Radio FFT模块结合stream to vector应用及Rotator频偏模块使用

文章目录 前言一、FFT 模块应用1、stream to vector 介绍2、创建 grc 图测试3、运行结果 二、频偏模块1、Rotator 简介2、创建 grc 图测试3、运行结果 前言 写个博客记录一下自己的蠢劲儿,之前我想用 FFT 模块做一些信号分析的东西,官方的 FFT 模块必须…

营销5.0时代,企业的痛如何解?

进入营销5.0阶段之后,许多企业都需解决连接客户效能低下的问题。针对这个问题,产品经理、软件开发公司包括个人开发者,要怎么找到有效的“解药”? 营销不仅每年都在变化,甚至每天都在变化。 ——现代营销学之父&…

【再探】设计模式—适配器、装饰及外观模式

结构型设计模式是用于设计对象和类之间关系的一组设计模式。一共有7种:适配器模式、装饰器模式、外观模式、桥接模式、组合模式、享元模式及代理模式。 1 适配器模式 需求:在软件维护阶段,已存在的方法与目标接口不匹配,需要个中…

论文阅读:RHO-1:Not All Tokens Are What You Need 选择你需要的 Tokens 参与训练

论文链接:https://arxiv.org/abs/2404.07965 以往的语言模型预训练方法对所有训练 token 统一采用 next-token 预测损失。作者认为“并非语料库中的所有 token 对语言模型训练都同样重要”,这是对这一规范的挑战。作者的初步分析深入研究了语言模型的 t…

记录一个练手的js逆向password

很明显 请求加密了password 全局搜索 有个加密函数(搜不到的可以搜临近的其他的关键字 或者url参数) 搜索的时候一定要仔细分析 我就没有仔细分析 我搞了好久 又是xhr又是hook的(还没hook到) 我当时也是疏忽了 我寻思这个也不是js文件 直到后来 我怎么也找不到 我就猜想 不…

代码随想录算法训练营DAY44|C++动态规划Part6|完全背包理论基础、518.零钱兑换II、377. 组合总和 Ⅳ

文章目录 完全背包理论基础完全背包问题的定义与01背包的核心区别为什么完全背包的循环顺序可以互换?CPP代码 ⭐️518.零钱兑换II思路CPP代码 ⭐️377. 组合总和 Ⅳ思路CPP代码 扩展题 完全背包理论基础 卡码网第52题 文章链接:完全背包理论基础 视频链接…

【数据结构与算法】力扣 102. 二叉树的层序遍历

题目描述 给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。 示例 1: 输入: root [3,9,20,null,null,15,7] 输出: [[3],[9,20],[15,7]]示例 2&#x…

上证50etf期权到底该怎么玩?

今天期权懂带你了解上证50etf期权到底该怎么玩?ETF期权是一种股票市场上的金融衍生品,它是在交易所上市交易的期权合约,其标的资产是某个特定的交易所交易基金(ETF),如上证50指数ETF或沪深300指数ETF等。 上…

Git命令Gitee注册idea操作git超详细

文章目录 概述相关概念下载和安装常见命令远程仓库介绍与码云注册创建介绍码云注册远程仓库操作关联拉取推送克隆 在idea中使用git集成add和commit差异化比较&查看提交记录版本回退及撤销与远程仓库关联 push从远程仓库上拉取,克隆项目到本地创建分支切换分支将…