从键盘热键到内存驻留:用DosBox+Masm6.15复活DOS时代的TSR黑科技
还记得DOS时代那些神奇的内存驻留程序吗?按下Alt+C弹出计算器,Ctrl+T显示系统时间——这些看似简单的功能背后,是80年代程序员对系统资源的极致利用。今天,我们将穿越回那个640KB内存就是奢侈品的年代,用DosBox和Masm6.15亲手实现一个真正的TSR(Terminate and Stay Resident)程序。
1. 为什么TSR是DOS时代的编程艺术
在Windows的预抢占式多任务出现前,DOS程序员们发明了TSR这种巧妙的"伪多任务"方案。一个典型的TSR程序执行流程是这样的:
- 程序正常启动执行
- 通过DOS中断将自己驻留在内存中
- 修改中断向量表挂钩特定事件
- 主程序退出但代码段保留在内存
- 触发事件时(如按键)执行驻留代码
这种技术催生了SideKick、Borland的Turbo系列编辑器等经典工具。理解TSR不仅能学习汇编,更能体会早期程序员在有限资源下的创造力。
注意:现代操作系统严格隔离进程内存空间,TSR技术已不再适用,但其中的中断处理、内存管理等概念依然有价值。
2. 搭建考古现场:配置Masm6.15的黄金组合
不同于简单的Hello World,TSR开发需要完整的MASM 6.15工具链。建议采用以下目录结构:
DOS_TSR_DEV
├── MASM6.15 # 编译器工具链
│ ├── BIN # 可执行文件
│ ├── LIB # 库文件
│ └── INCLUDE # 头文件
├── TSR_ASM # 项目代码
│ ├── CALC.ASM # 计算器TSR
│ └── CLOCK.ASM # 时钟TSR
└── UTILS # 辅助工具
在DosBox配置文件中建议添加这些自动挂载命令:
[autoexec]
mount c D:\DOS_TSR_DEV
c:
set PATH=%PATH%;C:\MASM6.15\BIN
set LIB=C:\MASM6.15\LIB
set INCLUDE=C:\MASM6.15\INCLUDE
关键环境变量说明:
| 变量名 | 作用 | 典型值 |
|---|---|---|
| PATH | 可执行文件搜索路径 | C:\MASM6.15\BIN |
| LIB | 链接库文件路径 | C:\MASM6.15\LIB |
| INCLUDE | 头文件路径 | C:\MASM6.15\INCLUDE |
3. TSR核心机制解剖:从理论到实践
3.1 内存驻留的魔法:DOS功能调用
实现TSR最关键的两步:
; 设置程序为驻留状态
mov ax, 3100h ; AH=31h(驻留退出) AL=返回码
mov dx, (TSR_END + 15) / 16 ; 需要保留的段落数
int 21h ; 调用DOS中断
计算驻留内存大小的经验公式:
段落数 = (代码结束地址 - 程序起始地址 + 15) / 16
3.2 中断劫持技术详解
要让TSR响应热键,需要接管键盘中断(INT 09h)。典型流程:
- 保存原中断向量
- 安装自定义处理程序
-
在自定义处理程序中:
- 调用原中断处理
- 检查特定热键组合
- 触发TSR功能
; 保存原中断向量
mov ax, 3509h ; AH=35h(获取中断向量) AL=09h(键盘中断)
int 21h
mov word ptr [OLD_INT09], bx
mov word ptr [OLD_INT09+2], es
; 设置新中断向量
mov dx, offset NEW_INT09
mov ax, 2509h ; AH=25h(设置中断向量) AL=09h
int 21h
4. 实战:编写热键弹出计算器TSR
让我们实现一个按Alt+C弹出简易计算器的TSR。完整代码框架如下:
.MODEL small
.STACK 100h
.DATA
OLD_INT09 DD ? ; 保存原中断向量
CALC_FLAG DB 0 ; 计算器激活标志
; 其他数据定义...
.CODE
START:
; 初始化代码...
jmp INSTALL_TSR
NEW_INT09 PROC FAR ; 新键盘中断处理
pushf
call DWORD PTR [OLD_INT09] ; 先调用原处理程序
in al, 60h ; 读取键盘扫描码
cmp al, 46h ; 'C'键扫描码
jne EXIT_INT
mov ah, 2 ; 获取键盘状态
int 16h
test al, 00001000b ; 检查Alt键
jz EXIT_INT
not [CALC_FLAG] ; 切换计算器状态
EXIT_INT:
iret
NEW_INT09 ENDP
CALCULATOR PROC ; 计算器界面实现
; 实现加减乘除逻辑...
ret
CALCULATOR ENDP
INSTALL_TSR:
; 安装中断处理程序...
mov dx, OFFSET INSTALL_TSR
mov cl, 4
shr dx, cl ; 转换为段落数
add dx, 10h ; 安全余量
mov ax, 3100h ; 驻留退出
int 21h
END START
编译这个TSR需要特殊步骤:
masm calculator.asm;
link calculator.obj;
exe2bin calculator.exe calculator.com
提示:TSR更适合编译为.COM格式,因为它从0100h开始加载,更易计算驻留大小。
5. 调试TSR的军火库:经典工具与技术
调试TSR需要特殊技巧,因为一旦驻留就无法用常规方法调试。推荐工具链:
-
DEBUG.COM :DOS自带的调试神器
-
查看内存:
d segment:offset -
反汇编:
u address -
设置断点:
g=start,end
-
查看内存:
-
INT 3技巧 :在TSR代码中插入
int 3触发调试器 -
内存查看命令 :
mem /d # 查看内存使用情况 mem /p # 查看驻留程序
常见问题排查表:
| 症状 | 可能原因 | 解决方案 |
|---|---|---|
| 系统崩溃 | 中断处理未正确保存寄存器 | 确保所有寄存器被保存/恢复 |
| 热键无响应 | 未正确检测键盘状态 | 检查Shift状态字节 |
| 驻留后功能异常 | 内存计算错误 | 重新计算驻留段落数 |
| 与其他TSR冲突 | 中断链处理不当 | 确保调用原中断处理程序 |
6. TSR进阶:从玩具到实用工具
要让TSR真正实用化,还需要考虑:
内存管理技巧 :
- 使用EMS/XMS扩展内存(如果有)
- 实现按需加载(Overlay技术)
- 提供卸载接口
UNINSTALL_TSR:
; 恢复原中断向量
mov dx, word ptr [OLD_INT09]
mov ds, word ptr [OLD_INT09+2]
mov ax, 2509h
int 21h
; 释放内存块
mov ah, 49h
mov es, word ptr [PSP_SEG]
int 21h
ret
多TSR共存方案 :
- 使用中断链技术
- 实现优先级管理
- 提供状态查询接口
在DosBox中测试不同配置:
| 配置项 | 推荐值 | 作用 |
|---|---|---|
| memsize | 16 | 模拟1MB内存(DOS常规+UMB) |
| cycles | max 10000 | 控制CPU速度模拟 |
| core | dynamic | 动态调整CPU周期 |
7. 从考古到启示:TSR技术的现代映射
虽然TSR技术已经过时,但其核心思想在现代开发中依然可见:
- 中断处理 → 事件驱动编程
- 内存驻留 → 后台服务/守护进程
- 热键触发 → 全局快捷键
- 中断链 → 中间件/Middleware
用现代Python实现类似功能:
import keyboard
import tkinter as tk
def show_calculator():
calc = tk.Tk()
# 计算器UI实现...
calc.mainloop()
keyboard.add_hotkey('alt+c', show_calculator)
keyboard.wait()
比较两种实现:
| 特性 | DOS TSR | 现代实现 |
|---|---|---|
| 触发机制 | 硬件中断 | 事件循环 |
| 内存管理 | 手动计算 | 自动GC |
| 并发处理 | 不可重入 | 多线程安全 |
| 开发效率 | 复杂 | 简单 |
| 系统影响 | 高风险 | 隔离安全 |
在DosBox中完整测试我们的TSR程序后,不妨思考:今天我们在容器、微服务中讨论的很多"新"概念,是否都能在40年前的这些技术中找到雏形?

266

被折叠的 条评论
为什么被折叠?



