Excuse me, everyone, how to write a 16-bit interrupt resident program in protected mode? I found the principle explanation part in dpmispec1, but I don't understand it very well. Could someone explain it in detail or give an example?
LLM 解释
在 DPMI(DOS Protected Mode Interface)规范下编写驻留程序(TSR)与传统的实模式 TSR 有本质区别。在 2010 年该帖子讨论的时背景下,开发者常受困于 DPMI 0.9 与 1.0 版本的实现差异。
1. 技术背景与 DPMI 版本限制
DPMI 0.9 是最广泛实现的规范(如 Windows 3.x/9x 的 DOS 窗口、早期的 CWSDPMI),但它主要为“短暂运行”的保护模式程序设计。DPMI 1.0 增加了对驻留服务提供者(Resident Service Providers)的支持,即用户提到的 AX=0C00h(安装驻留项)和 AX=0C01h(终止并驻留)功能。
由于大多数 DPMI 宿主(Host)仅支持 0.9 版本,直接调用 1.0 规范的 0C00h 类函数通常会返回 8001h(不支持该功能)错误。
2. 保护模式驻留的核心障碍:选择子(Selectors)
在实模式下,TSR 只需保留内存并修改中断向量表。但在保护模式下,程序依赖于 DPMI 宿主分配的描述符(Descriptors)和选择子(Selectors)。
-
清理机制:当一个 DPMI 0.9 客户端退出(即使通过
INT 21h AH=31h)时,DPMI 宿主会认为该任务已结束,并回收该任务持有的所有选择子。
-
后果:即使程序的代码和数据仍留在内存中,其中断处理函数所依赖的选择子也会失效。一旦中断触发,由于段选择子不再合法,会导致 CPU 触发一般保护异常(GPF)并挂起系统。
3. DPMI 0.9 下的替代方案
在不支持 DPMI 1.0 驻留功能的环境下,实现“保护模式中断处理”通常有以下几种方式:
A. 实模式驻留 + 保护模式入口
这是最常见的做法。程序主体作为实模式 TSR 驻留。当保护模式应用程序运行时,它通过 INT 31h 的 0300h(模拟实模式中断)或回调机制与该实模式 TSR 通信。这并非严格意义上的保护模式 TSR,但避开了选择子失效的问题。
B. 拦截保护模式中断(仅限运行期间)
如果只需要在程序运行期间拦截中断,可以使用 DPMI 0.9 标准功能:
-
AX=0204h:获取保护模式中断向量。
-
AX=0205h:设置保护模式中断向量。
-
注意:在程序退出前,必须使用
0205h 将原中断向量还原。如果不还原,程序退出后中断触发将导致系统崩溃。
C. “伪驻留”方案(常驻进程)
在某些环境下(如单任务 DOS + CWSDPMI),可以通过不退出程序的方式保持驻留。但这会占用控制权,不符合传统 TSR 的定义。
4. DPMI 1.0 驻留流程简述
如果环境支持 DPMI 1.0(如某些特定版本的扩展器),实现 16 位保护模式驻留的步骤如下:
-
检测支持:通过
INT 31h AX=0400h 检查 DPMI 版本。
-
分配驻留描述符:使用
AX=000Dh 标记特定描述符为驻留。
-
挂接中断:使用
AX=0205h 挂接保护模式中断向量。
-
调用驻留退出:执行
AX=0C01h。此时 DPMI 宿主不会回收该任务指定的选择子和内存。
5. 总结
在 2010 年甚至现在的模拟/复古开发环境中,由于 DPMI 0.9 的统治地位,不存在一种通用的、仅依赖 DPMI 0.9 规范就能实现的“保护模式退出并驻留”的方法。
对于开发者而言,若要在 DOS 环境下实现稳定的硬件中断处理,推荐的做法是:
-
实模式驻留:负责处理最底层的硬件中断(如定时器、串口)。
-
DPMI 回调(Callback):如果保护模式程序需要感知这些中断,利用
AX=0303h 分配实模式回调地址,让实模式 TSR 在触发时调用保护模式代码。
这种“实模式核心 + 保护模式接口”的混合架构是 16 位与 32 位 DOS 扩展时代的标准工程实现。