汇编语言键盘读取:从按下按键到获取字符的底层过程

汇编语言键盘读取:从按下按键到获取字符的底层过程

你在写一个极简操作系统,或者想深入理解程序如何“看到”你敲下的每一个键?这时候就得碰一碰汇编语言中的键盘读取。它不像高级语言调用 getchar() 那样轻松,但能让你真正看清硬件和软件之间的对话。

键盘不是直接传字符的

很多人以为按下 A 键,电脑就立刻得到字母 'A'。实际上,键盘每次按键都会产生一个叫“扫描码”的东西。这个码分两种:通码(make code)表示按下,断码(break code)表示松开。比如按一下 A,键盘先发一个 0x1E,松开时再发一个 0x9E。

CPU 怎么知道有扫描码来了?靠的是中断。键盘控制器连接到主板上的中断控制器,通常触发的是 IRQ1,对应中断向量号 0x21。只要按键,CPU 就会暂停当前任务,跳去执行键盘中断服务程序。

读取端口获取扫描码

在 x86 架构下,键盘控制器通过 I/O 端口与 CPU 通信。扫描码会被送到端口 0x60。所以,在中断处理函数里,最核心的操作就是从这个端口读数据。

in al, 0x60

这一句汇编指令就把扫描码读进了 AL 寄存器。接下来就要判断它是通码还是断码。通常,高比特位为 1 的是断码,比如 0x9E,你可以用测试指令检查:

test al, 0x80
jnz key_release ; 如果是释放事件,跳走

转换扫描码为 ASCII

有了通码,比如 0x1E,怎么变成 'A'?你需要一张映射表。可以定义一个字节数组,把常见扫描码对应到 ASCII 码:

keymap:
.byte 0 ; 0x00 无
.byte 0 ; 0x01 Esc
.byte '1' ; 0x02
.byte '2' ; 0x03
; ... 忽略中间部分
.byte 'a' ; 0x1E 对应 A 键

然后用扫描码做索引查表:

mov bl, al      ; 扫描码存入 BL
and bl, 0x7F ; 清除高位,得到通码索引
mov al, [keymap + ebx] ; 查表得 ASCII

当然,这还没考虑 Shift 或 Caps Lock 的情况。如果需要支持大小写,就得额外记录修饰键状态,动态调整映射结果。

实际应用场景

你在写一个启动加载器(bootloader),系统还没加载任何操作系统的输入模块,这时候只能靠自己处理键盘。或者你在开发嵌入式设备的固件,资源紧张,连 C 库都没有,直接上汇编读键盘就成了唯一选择。

哪怕现在大多数开发都不需要手写中断处理,但明白这个流程,能帮你更清楚地理解 getchar、scanf 甚至浏览器输入框背后到底发生了什么。下次你敲密码时,不妨想想那串字符是怎么从指尖一路跑到内存里的。