windows电脑打开记事本,键盘按下A,从键盘线发送信号开始,到屏幕显示A,经历的全部过程是什么?
不同类型的硬件(比如是USB键盘还是PS/2键盘),不同的软件(比如不同版本的Windows)具体的过程区别很大。
先从键盘内部说,键盘的本质是开关矩阵,里面的逻辑电路画出来是下面这样的:

图片来源:USB Keyboard Using MSP430 Microcontrollers
不同的键盘电路上会有一些差异,比如号称全键无冲的机械键盘和最便宜的薄膜键盘,在电路层面上是有差异的,有些贵的键盘会给每个键位单独设计一个开关回路,有些便宜的键盘就是一个共享的开关矩阵,后者的问题是当按下很多键时,会有幽灵按键触发:

键盘的硬件电路上,会周期性的扫描是否有开关导通(所以扫描码,scancode就是这么来的),如果有,则记录下这个开关对应的键码,保存到键盘内部的一个缓冲区内,这个缓冲区不大,一般只有1-8字节之间,也就是只能记录1-8个键。
从这里开始,就有了硬件实现的分叉,如果是PS/2接口的键盘,会直接通过PS/2接口上报按键的通断状态,PS/2接口是串行信号,以10000~16666 Hz的频率上报按键的比特位(注意是位不是字节)。
串行信号会发送到PC机的i8042键盘控制器芯片(仅讨论PC,不讨论其它架构),i8042收到串行信号以后向主机上报键盘中断。早年的i8042是一个真实的芯片,现代的i8042的功能已经整合到了主板南桥的芯片中,不再独立存在。
关于i8042芯片的资料:I8042 PS/2 Controller - OSDev Wiki,i8042芯片会以IRQ中断的方式直接把按键事件上报给CPU,过去用的是8259控制器,现在用的是IO APIC(参考:IOAPIC - OSDev Wiki)。
如果是USB键盘,则稍微复杂一点,USB键盘不直接上报按键。USB协议是轮询的,任何USB请求必须由USB主机发起,所以对于USB键盘,是PC机器主机软件通过主机的USB控制器发起轮询请求,询问是否有USB报文。这个具体的轮询过程在硬件层面上非常复杂,USB协议很大,从电气信号讲起来的话,也有接近上百页的文档。总之,USB与PS/2的最大不同是:PS/2键盘可以主动上报,USB只能等主机轮询。
USB键盘通过interrupt传输(中断传输)来上报数据,虽然叫中断传输,但是USB里的这个中断跟PC机里的中断概念完全没有关系。
USB键盘通过HID report的方式上报当前有哪些键被按下:

可以看到USB键盘的report一次最多能报告6个键,这也是为什么有些“高端”键盘仍然采用PS/2接口的原因,PS/2能上报的同时按下的键位数量是没有限制的(取决于传输带宽)。
USB HID规范:
Microsoft Word - HID1_11.doc因为USB要通过HID report方式上报,所以在Windows驱动层面上,解析按键的逻辑更复杂一些,Windows需要记录每次report里上上报的按下的键位,然后获得“按下”和“弹起”这两个事件。
具体展开说还是有点复杂的:
比如上一个report里报告按键“A”被按下,下一个report里报告没有任何按键被按下,那么从上一个report到这一个report之间的时间里,一共对应了三个Windows的事件:KEY_DOWN,KEY_UP,KEY_PRESS。
USB和PS/2还要考虑一种情况:你如果持续按下A不松手,记事本里仍然会持续显示A,这是因为当有按键按下时,驱动层里有一个计时器记录这个被按下的键位的开始时间,如果超过了一个阈值,那么会持续向应用软件报告按键事件。
所以,到这里对键盘按键从硬件到操作系统边界这里做一个总结:
PS/2键盘采用的是串口信号 -> i8024键盘控制器 -> 上报按键中断给CPU -> PS/2键盘控制器驱动解码按键事件。
USB键盘采用的是等待主机轮询并上报 HID report -> USB协议 -> USB键盘控制器解码按键键码并生成按键事件。
具体CPU怎么挂中断的过程就不说了,有兴趣可以去读Intel的手册:Manuals for Intel® 64 and IA-32 Architectures
下面就是Windows操作系统层面的事情,当PS/2或者USB键盘驱动解码按键事件以后,会调用KeyboardClassServiceCallback上报按键事件,到这里,键盘已经以一个公共的键码方式报告给KeyboardClassServiceCallback了,在这个层面上,还有filter驱动这也是很多黑客工具搞键盘监听的时候喜欢操作的一个位置。
这个函数位于kdbclass驱动里,在这里,键盘会以一个内核设备的方式存在并允许Windows内核以zwCreate/zwReadFile等方式访问。
另外,DirectX的direct input有些也工作在这一层附近,但具体的技术细节我不太了解。
到这里,就已经是Windows的内核边界了。
出了内核边界,另外一个重要的环节就是csrss.exe,所有常见的用户态输入(比如按键消息)都是由这个进程负责分发完成的。(注:某些Windows版本的某些功能会集成到conhost.exe)
写过Windows应用程序的都知道Windows有个用户态的键盘钩子的概念,能监听键盘(虽然大部分情况下会默认是病毒),用的是SetWindowsHook:

SetWindowsHookExA function (winuser.h) - Win32 apps | Microsoft Learn
SetWindowsHook就工作在csrss这个层级上,钩子分系统和窗口两种,具体的区别就是能监听的范围是整个系统还是某个窗口。这个API可以在发送前监听,也可以在发送给具体某个窗口时监听。这个钩子函数非常恶心,早年很多病毒就是基于它工作的,它能卡死整个系统。
Windows的各种图形应用程序以Windows消息环的方式接收并处理信息,老式的Windows应用大概都有这样的代码:

键盘的事件会发送一个WndProc里:

以上代码参考:Using Messages and Message Queues - Win32 apps | Microsoft Learn
这里就是应用程序接收按键事件的具体过程了。
对于记事本来说,具体的实现代码就看记事本怎么处理了。简单的记事本程序可以是一个默认的Windows的TextBox控件,默认的TextBox甚至可以做到完全不需要用户新写代码处理任何事件就能做到基本的输入、输出功能。
这里,假设记事本用的就是默认的TextBox。
TextBox收到要输出的文本以后,会调用InvalidateRect重绘相关区域,InvalidateRect相当于清空原始显示区域。如果用老的Windows,在系统比较卡的情况下,会观察到输入到显示的过程之间,要新显示区域里,先变成一个白色方框,然后再显示字符。这是因为过去的Windows用户态应用程序在显示新文本的时候,是先擦除,再重绘,在系统特别卡的时候会看到这两步是分开的。
字符显示,这里可能走的是传统的GDI API,也可能是走的direct draw等接口。这部分的实现细节我不了解,我学Windows编程的时候还是以GDI为主,但是据说现在应用很多都改成走direct draw了。
显示字符的过程中,还涉及到字符编码转换和字符的字库解析的过程。字库的加载并不在按键的处理过程,字库一般是在应用程序启动的时候就加载了,之后就一直缓存。字符编码转换具体在哪个层面上做的,细节我不太了解,但是都跟C:\WINDOWS\SYSTEM32下面的一堆C_开头的NLS文件有关,文件名里的数字就是code page的编号:

同样一个按键A,对应到不同语言里,可能对应着不同的字符(非英语国家),所以要通过NLS(national language support)文件翻译成统一的Unicode。
Windows通过NLS完成字符->本地化字符的翻译(翻译成Unicode),通过TextOut API借助字库完成字符->图像像素的解析,通过EndPaint API完成从绘图过程。
准确的说GDI并不能直接操作显存,只有DirectX接口才能,这里的实现细节在不同的Windows版本中有不同的实现,新一点的Windows(Win10/11)似乎已经改良了GDI的实现,这部分细节我不了解。
但是可以肯定的是,不管采用什么接口,用户的显示的文本数据最终会以类似bitmap的方式发送到显存里,那是类似于一个Linux的framebuffer一样的东西。Windows上使用WDDM这个框架来完成显示输出,显卡GPU会周期性扫描这个framebuffer,然后把图像信号输出到VGA/HDMI/DP接口上。这部分是纯硬件操作,并且大部分厂商不公开这部分的实现细节,无从参考。
这里,提供一份很古老的BIOS显存的文字模式的实现流程:
x86 PC机的0xB8000地址(物理地址)是显存的彩色文本显示区映射,两个字节对应一个字符,VGA显卡会周期性扫描这个内存区域,解析其中的字符,然后VGA显卡会自带一个字体库(负责显示ASC码),翻译成点阵形式的图形,输出到VGA接口上。
显示器从VGA/HDMI/DP接口获得模拟或者数字信号,通过电子枪(老式的显示器)或者液晶屏幕开关完成屏幕上的点阵字符显示。
考虑到不同显示器的设计区别很大,这里就不展开了,也没办法展开,技术细节太多,也没有统一的规范,有兴趣的可以找一个嵌入式的开发板,找那种最简单的屏幕模块,尝试编程操作一下,方便理解。
总结一下整个工作流程的相关知识体系:
键盘->主机的过程:
1. PS/2协议,串口协议,i8042键盘控制器,中断控制器
2. USB协议,HID协议
主机->显示的过程:
内核:Windows驱动框架,Windows键盘驱动框架(KBDCLASS.SYS)
用户态:CSRSS框架,Windows消息环结构,Windows GDI API
内核:WDDM显卡驱动框架
显示的物理过程:
VGA/HDMI/DP协议