int 0x16键盘输入在286 PC-AT实模式OS下无法读取故障排查
故障根因定位
你的代码存在两处兼容性问题,虚拟机BIOS容错性强可以正常运行,286实机的老版BIOS无容错逻辑,直接触发故障:
- badChar按键过滤循环存在越界死循环问题
你实现的非法扫描码过滤逻辑有明显缺陷:- 加载
bx为badChar表首地址后,进入循环先执行inc bx,直接跳过了表中第一个待过滤的扫描码 - 循环未做边界判断,一旦因为内存内容偏差没有遍历到badChar表末尾的0终止符,会持续向后遍历随机内存,永远不会退出循环,代码直接卡死在该循环内,根本不会回到
int 0x16读按键的逻辑,表现为完全不响应键盘输入。
虚拟机启动后内存大多被初始化为全0,越界遍历很快就能碰到0值退出循环;286实机上电后内存内容是随机的,很容易出现长时间遍历不到0的情况,直接触发死循环。
这也解释了键盘锁定灯可正常触发、Ctrl+Alt+Del可正常重启的现象:这些功能是BIOS的键盘硬件中断(IRQ1对应int 0x9)服务程序直接处理的,你的代码死循环时没有关闭中断,硬件中断可正常响应,只是用户层代码卡死,永远执行不到读键盘缓冲区的逻辑。
- 加载
- 调用int 0x10电传输出功能时未设置BH寄存器
int 0x10的ah=0x0e功能明确要求传入bh = 目标显示页码,bl = 图形模式下的前景色。你的代码仅设置了bl为颜色值,bh保留了之前bx(输入缓冲区指针)的高8位,在286实机搭载的老EGA/CGA显卡BIOS上,如果传入不存在的显示页码,调用会直接返回不输出字符,部分老BIOS甚至会破坏寄存器状态,导致后续流程跑飞。
修复步骤
- 第一步:代码开头增加远跳转固化cs:ip,手动初始化段寄存器和栈,避免不同BIOS跳转MBR的方式差异导致地址计算错误:
[org 0x7c00] jmp 0x0000:entry ; 强制cs=0,ip=entry,和org 0x7c00的地址约定完全匹配 entry: xor ax, ax mov ds, ax mov es, ax mov ss, ax mov sp, 0x7c00 ; 栈设置在0x7c00以下,不会和后续加载的程序内容冲突 sti
- 第二步:重写badChar过滤循环逻辑,避免跳过首元素,增加终止判断彻底杜绝死循环:
waitForKey: xor ah, ah int 0x16 push bx push cx mov bx, badChar cmpLoop: mov cl, [bx] test cl, cl ; 碰到0终止符立刻退出循环 jz endCmp cmp ah, cl je popBack inc bx jmp cmpLoop endCmp: pop cx pop bx jmp continue popBack: pop cx pop bx jmp waitForKey
- 第三步:调用int 0x10输出字符前,手动将bh设为0(第0显示页),不要依赖BIOS的默认值:
mov ah, 0x0e push bx mov bh, 0 ; 固定使用第0显示页,兼容老显卡BIOS mov bl, [colourByte] int 0x10 pop bx
额外兼容性建议
- 调用
int 0x13读扇区后必须检查CF标志,读盘失败时要重置磁盘控制器重试,老286的软盘控制器容错性极差,不做错误处理很容易加载到错误的内存内容。 - 实模式开发不要假设BIOS初始化的段寄存器、栈指针符合预期,所有段寄存器、栈指针必须手动初始化,不能依赖BIOS默认值。
内容的提问来源于stack exchange,提问作者Skeledog
相关产品推荐
相关产品推荐

