在现代操作系统兼容层的设计中,通常有两种主流路径:一是以 Wine 为代表的女巫(Wine Is Not an Emulator)模式,在用户空间重实现 Windows API;二是以内核模块形式,直接在系统调用层面进行拦截与翻译。2026 年出现的 WSL9x 项目则走出了一条极为特殊的第三条路 —— 它不是让 Windows 程序运行在 Linux 上,而是让完整的 Linux 内核运行在 Windows 9x 内部。这一逆向兼容性尝试揭示了 9x 时代操作系统架构的诸多技术细节,尤其是分段内存模型与中断处理机制如何与现代 Linux 系统调用发生碰撞。
Windows 9x 的中断描述符表限制
理解 WSL9x 的设计思路,需要先回顾 Windows 9x 在系统调用层面的硬件限制。Linux 自 1.0 以来采用 int 0x80 指令触发系统调用,这是 i386 架构提供的传统机制。当用户态程序执行 int 0x80 时,CPU 会查找中断描述符表(IDT)中的第 128 号向量(0x80),进入内核态执行相应的系统调用处理程序。现代 Linux 在支持 sysenter/sycall 指令的同时,仍然保留了对 int 0x80 的兼容支持。
问题在于,Windows 9x 的 IDT 结构与后续版本存在显著差异。Windows 9x 构建于 MS-DOS 时代的 BIOS 中断向量基础之上,其 IDT 表项数量和布局受到 16 位引导加载历史的深刻影响。具体而言,Windows 9x 的 IDT 并未预留足够的空间来安全地挂载 0x80 号向量的处理程序。这并非设计缺陷,而是 9x 内核将更多的中断向量预留给 VxD(虚拟设备驱动程序)系统所致。WSL9x 的作者在项目文档中明确指出:「Win9x does not have an interrupt descriptor table long enough to install a proper handler for int 0x80」。
这一硬件层面的约束迫使 WSL9x 采用了一种极具创造性的绕过方案:既然无法直接安装 int 0x80 handler,那就将 int 0x80 指令本身变成一个可被捕获的错误。
GPF handler 的指令拦截机制
通用保护错误(General Protection Fault)是 i386 架构用于报告权限违规的异常类型。当程序执行违反 CPU 保护模型的指令时 —— 例如在用户态尝试访问特权寄存器、或触犯内存保护规则 ——CPU 会触发 GPF 并将控制权转交给 IDT 中预设的异常处理程序。WSL9x 正是利用了这一机制,将 Linux 的系统调用指令 int 0x80 转化为可被拦截的 GPF。
其工作流程如下:当 Linux 内核在用户空间执行 int 0x80 指令时,由于 9x 内核并未为 0x80 向量安装有效的处理程序,CPU 会退而求其次地触发一个保护错误。此时,WSL9x 的 VxD 驱动所注册的 GPF handler 获得控制权。该 handler 首先检查触发异常的指令是否为 int 0x80—— 这可以通过读取导致 fault 的指令字节来确认。一旦识别成功,handler 会模拟指令成功执行的效果:推进指令指针(Instruction Pointer),使其跳过 int 0x80 这条指令,然后执行真正的系统调用分发逻辑。
这种设计的精妙之处在于,它完全绕过了 IDT 扩展的问题。VxD 驱动在 Windows 9x 中拥有极高的特权级别,可以自由地注册 GPF 处理回调。与其修改 IDT 结构(这在 9x 内核中极其危险且不稳定),不如利用现有的保护错误机制完成拦截。
VxD 驱动的双重角色
WSL9x 中的 VxD 驱动承担了远超过普通设备驱动的职责。从架构上看,VxD(Virtual Device Driver)是 Windows 9x 提供的特权级组件,运行在 Ring 0,拥有访问硬件和管理虚拟内存的完整能力。WSL9x 的 VxD 实际上扮演了两个关键角色:系统调用网关与内存管理协调者。
作为系统调用网关,VxD 的 GPF handler 需要完成从 Linux 系统调用号到 Windows 9x API 的语义映射。Linux 的系统调用编号(如 exit=1、fork=2、write=4 等)与 Windows 9x 的 Win32 API 子系统不存在直接对应关系。WSL9x 的解决方案是将 Linux 内核本身进行补丁修改,使其直接调用 Windows 9x 的内部 API 而非标准的 POSIX 接口。换言之,运行在 WSL9x 环境中的 Linux 内核已经被改写,不再产生 int 0x80 调用,而是通过 VxD 提供的入口点完成特权操作。
作为内存管理协调者,VxD 还需要处理 Windows 9x 的分段内存模型与 Linux 的平坦内存模型之间的差异。Windows 9x 虽然已经支持 32 位保护模式,但其内存管理仍然保留了 16 位段寄存器的残影。Linux 则采用经典的平坦模型(Flat Model),整个 4GB 地址空间对每个进程可见。WSL9x 必须在这两种模型之间进行地址空间切换,确保 Linux 内核的页表设置能够正确映射到 Windows 9x 的物理内存布局中。
16 位 DOS 客户端的 TTY 转接
WSL9x 的第三个组件是一个极其精简的 16 位 DOS 程序,其唯一职能是将 DOS 提示符传递给 Linux 内核作为 TTY(终端)设备。在现代 WSL 环境中,Linux 子系统可以直接与 Windows 的控制台子系统交互,但在 Windows 9x 上这种整合并不存在。DOS 程序作为一个轻量级的桥接层,负责捕获用户输入并转发给 Linux 内核,同时将内核输出回显到屏幕。
这个设计体现了对 9x 时代软硬件约束的充分尊重。在那个年代,纯粹的 32 位控制台子系统尚未成熟,大多数交互仍然依赖 DOS 层面的字符处理能力。选择一个 16 位程序而非 32 位 Win32 应用,可以确保最大的兼容性 —— 它不会触发任何 Win32 子系统的依赖,也不会受到 DLL 版本冲突的影响。
工程权衡与安全边界
必须指出的是,WSL9x 的设计并非没有代价。由于 Linux 内核运行在 Windows 9x 的同一特权等级(Ring 0),两者实际上共享了同一份 CPU 上下文。这意味着任何一方的崩溃都会导致整个系统重启 —— 不存在现代操作系统中的进程隔离保护。WSL9x 的作者在 Hacker News 上也承认:「They are supposed to cooperate, but if either crashes then both go down」。
从系统设计的角度看,WSL9x 提供了一个极端环境下的技术验证案例。它展示了一个核心工程原则:当标准路径被阻塞时,创造性的错误处理机制可以开辟新的通路。通过将系统调用重新定义为保护错误,WSL9x 规避了 IDT 空间不足的硬件限制,同时利用 VxD 的特权地位实现了两种操作系统内核的共存。这种 GPF 拦截思路在现代操作系统的兼容层设计中具有独特的参考价值,尽管其应用场景极为特殊。
资料来源:The Register 报道《WSL9x hacks Linux into ancient Windows 9x systems》(2026 年 4 月 22 日)及 WSL9x 项目 Codeberg 仓库。