DOSBOX下MASM编写TSR程序键盘无响应问题求助及开发建议
问题解决与DOS TSR编程建议
代码错误分析与修复
你的TSR程序导致DOS键盘无响应,核心是以下几个致命错误:
- 非法寄存器压栈:中断处理程序里的
push si0是笔误,应为push si,非法寄存器会破坏栈结构,导致中断返回异常。 - 数组索引逻辑错误:
mov si, x中x未定义,正确做法是将8位的CurrentRate扩展为16位索引(数组索引需要16位寄存器)。 - 中断处理流程错误:调用旧中断的方式导致栈失衡。你用
pushf + push旧段偏移 + retf直接跳转到旧中断,旧中断执行iret后直接返回被中断的程序,当前中断处理程序中压栈的寄存器从未被弹出,栈持续失衡最终导致系统崩溃。 - 常驻数据存储错误:
INT 27h仅保留CS段的内存,你将常驻数据放在DATA段,程序退出后DOS会回收DATA段,中断处理程序访问这些数据时会读取到垃圾值。 - INT 27h参数错误:
dx需指向常驻部分的末尾,你设置为offset start会导致仅极小部分代码被保留,后续执行必然出错。
修正后的代码
.MODEL small .STACK 100h .CODE ; 把所有需要常驻的数据移到CODE段 old_handler_offset DW 0 old_handler_segment DW 0 TickCounter db 0 CurrentRate db 0 RepeatRates db 32 dup (?) InitRepeatRates proc mov cx, 32 mov si, offset RepeatRates mov al, 1Fh ; 最慢重复率对应值 FillLoop: mov [si], al dec al inc si loop FillLoop ret InitRepeatRates endp int_handler proc far push ax push bx push dx push si push es push di ; 每秒更新一次(DOS时钟中断18次/秒) inc cs:TickCounter cmp cs:TickCounter, 18 jl skip_update mov cs:TickCounter, 0 ; 获取当前要设置的重复率索引 movzx si, cs:CurrentRate ; 将8位CurrentRate扩展为16位索引 ; 更新索引,循环0-31 inc cs:CurrentRate cmp cs:CurrentRate, 32 jb no_reset mov cs:CurrentRate, 0 no_reset: ; 发送键盘重复率设置命令 wait_input_ready: in al, 64h test al, 02h jnz wait_input_ready mov al, 0F3h ; 键盘设置重复率命令 out 60h, al wait_data_ready: in al, 64h test al, 02h jnz wait_data_ready mov al, cs:RepeatRates[si] out 60h, al skip_update: ; 调用旧的时钟中断处理程序 pushf call dword ptr cs:old_handler_offset ; 直接远调用旧处理程序 ; 恢复寄存器 pop di pop es pop si pop dx pop bx pop ax iret int_handler endp ; 标记常驻部分的起始 start: mov ax, @CODE mov ds, ax ; 因为数据现在在CODE段,所以DS指向CS call InitRepeatRates ; 获取旧的INT 1Ch中断向量 xor ax, ax mov es, ax mov ax, es:[1Ch*4] mov old_handler_offset, ax mov ax, es:[1Ch*4+2] mov old_handler_segment, ax ; 设置新的中断向量 cli mov word ptr es:[1Ch*4], offset int_handler mov ax, seg int_handler mov word ptr es:[1Ch*4+2], ax sti ; INT 27h:dx指向常驻部分的下一个字节,确保所有代码数据都被保留 mov dx, offset end_resident int 27h ; 常驻部分结束标签 end_resident: END start
DOS TSR编程通用建议
- 数据存储:将常驻数据放入CS段:
INT 27h和INT 31h(DOS 4+)默认仅保留CS段内存,DATA段会被DOS回收。所有中断处理程序需要访问的数据必须放在CS段,或通过INT 31h明确申请常驻内存块。 - 中断处理流程必须严谨:
- 进入中断后立即保存所有用到的寄存器(包括段寄存器),退出前恢复。
- 调用旧中断处理程序时,必须通过
pushf + call far的方式,确保旧中断执行完后回到当前程序,再恢复寄存器并执行iret,绝对不能用retf直接跳转(会导致栈失衡)。 - 禁止在中断处理程序中调用可能触发重入的DOS功能(如
INT 21h的大部分子功能),如需调用必须先关闭中断或使用重入安全的子功能。
- 正确设置中断向量:修改中断向量前必须执行
cli关闭中断,修改完成后sti开启中断,防止中断发生在向量修改过程中导致系统崩溃。 - 常驻范围精确控制:使用
INT 27h时,dx必须指向所有需要常驻的代码和数据的下一个字节,确保没有遗漏也没有多余的内容被保留。如果使用INT 31h(推荐),可以更灵活地申请和释放常驻内存。 - 测试与卸载:TSR程序一旦加载很难直接卸载,测试时尽量在虚拟机(如DOSBox)中进行,避免破坏物理DOS环境。如需支持卸载,需在常驻代码中保存原始中断向量和内存块信息,提供卸载入口。
- 避免硬件访问冲突:直接访问硬件(如键盘端口60h/64h)时,必须严格按照硬件时序操作(如等待输入输出缓冲区就绪),否则会导致硬件状态混乱。
内容的提问来源于stack exchange,提问作者Youvii
相关产品推荐
相关产品推荐

