keelOS: 远古系统?我们也不会放弃!
Windows XP SP2 和 Windows Server 2003 R2 已经在 keelOS 上装好能用,用的全是系统自带的驱动,不用往 guest 里装任何东西。为此 keelOS 给 bhyve 新写了 3 个设备(IDE、PCnet 网卡、UHCI),接上了 SeaBIOS 和 VGA/VBE 显示,并修正了原版 bhyve 在老系统面前才会暴露的一批错误。
为什么还要管老系统
老系统没有消失,它们跑在虚拟机里。小公司的财务软件、工控程序、只认某个版本 Windows 的专用工具,很多年前就从物理机搬进了 VMware,一直跑到今天。
Broadcom 改了 VMware 的许可方式之后,受影响最大的是小公司和家用实验室,正是 keelOS 的用户。要接住这批用户,他们手里的老虚拟机就得能跑。
在原版 bhyve 上装 Windows XP 走不通,原因有三层:
- 固件:老系统从传统 BIOS 引导。bhyve 的固件是 UEFI,上游 EDK2 已经删掉了兼容模块(CSM)。
- 设备:bhyve 的磁盘是 AHCI、NVMe、virtio,网卡是 virtio 和 e1000,USB 是 xHCI。Windows XP 一个驱动都不带。
- 细节:老系统用的是 8259 中断控制器、PIT 时钟、实模式、VGA 寄存器。这些路径在 bhyve 里很少有人走,错误一直没被发现。
keelOS 的做法是对标 ESXi 和 QEMU/KVM:它们给老系统什么虚拟硬件,我们就给什么。这样迁移过来的虚拟机里驱动已经齐了,不用改 guest。
老系统要的是另一套虚拟硬件
从固件到中断控制器,老系统和新系统看到的是两台不同的机器。
左边是 bhyve 原有的、新系统在用的硬件。右边是老系统认得的硬件,上面五层是 keelOS 补上的,最下面一层 bhyve 本来就有,但有错。
固件:直接用 SeaBIOS
传统 BIOS 引导的 guest 直接用 SeaBIOS 当固件,和 QEMU/KVM 一样。bhyve 本来就有 SeaBIOS 需要的大部分接口(fw_cfg、ACPI 表、E820),要补的是让 SeaBIOS 认出这台机器:
- 主机桥装成 Intel i440fx。SeaBIOS 只有看到 QEMU 的主机桥才用 fw_cfg。
- 固件的最后 128K 同时放在 1 MB 以下。CPU 复位后跳到的地址在那里,bhyve 原来只把固件映射在 4 GB 顶端。
- ACPI 的入口(RSDP)放进 F 段,其余的表只经 fw_cfg 交给固件。
SeaBIOS 起来之后,Debian 13 第一次就引导到了登录。FreeBSD 却停在引导程序里,查出来的是原版 bhyve 的一个错:
| 现象 | 原因 | 修正 |
|---|---|---|
| FreeBSD 的 gptboot 转了 35 圈就停住 | BIOS 给 PIT 写计数 0(8254 把 0 当成 65536,也就是 18.2 Hz 的时钟滴答)。bhyve 遇到 0 什么都不启动,guest 永远等不到 IRQ 0 | 先把 0 换成最大计数再启动定时器 |
| SeaBIOS 报 I/O 地址空间用完 | 探测 BAR 大小时 bhyve 读回的高 16 位是 0,SeaBIOS 把 64 字节的 BAR 算成将近 4 GB | 读回写入的高位,和 QEMU 一样 |
Linux、FreeBSD 内核和 UEFI 固件都写非零的计数,所以 PIT 这个错一直没人碰到。我们在第二台开发机上用原生的 FreeBSD 14.5 做了对照:原版卡住,只加这一个修正就能到登录。FreeBSD 上游在 2026-09-21 也修了同一处。
SeaBIOS 是 LGPLv3,作为单独的固件文件发布,同时提供源码,用户可以换成自己编的。在 keelOS 里只要在虚拟机配置里写 "bootrom": "bios"。
显示:VGA 和 VBE
老系统的画面分三段:开机时的 VGA 文字模式,安装程序的 16 色图形模式,进系统后的 VBE 高分辨率。bhyve 里有一份 VGA 模拟的代码,但原版遇到 vga=on 直接拒绝启动,VBE 则完全没有。
| 现象 | 原因 | 修正 |
|---|---|---|
| 画面全黑 | guest 写显存地址 0xB8000 时写进了普通内存,VGA 模拟看不到 | 低端内存空出 0xA0000–0xBFFFF 给 VGA |
bhyve 退出:Failed to emulate instruction |
指令模拟器一开头就拒绝实模式 | 补上实模式和 16 位寻址 |
| FreeBSD 内核起来后屏幕变黑 | bhyve 的 ACPI 表里写死了“没有 VGA” | BIOS guest 不写这个标志 |
| Windows 10 安装界面黑屏 | Windows 8 以后的基本显示驱动要 VBE 的线性帧缓冲 | 实现 Bochs VBE 接口,配 SeaVGABIOS |
| 日文版 Server 2003 的安装界面只有一条条蓝色块 | bhyve 的 VGA 写模式有三处错:AND、OR、XOR 从不执行,旋转计数取错,位掩码以外的值丢失 | 四种写模式按 VGA 的定义重写 |
| Windows 的显示适配器报代码 12 | ACPI 里 PCI 总线的资源窗口没有 VGA 显存,也不包括 SeaBIOS 分配 BAR 的地址 | 资源窗口补上这两段 |
日文版的安装程序不用文字模式,它在 640×480 16 色的图形模式里自己画汉字,所以才碰到了写模式的错。英文版 XP 用文字模式,之前一直没暴露。
修代码 12 时还带出一个问题:Windows 用 SSE 指令往显存写数据,指令模拟器不认识,bhyve 直接退出。现在模拟器支持 MOVUPS、MOVDQA 这一组 SSE 移动指令。
VNC 和 RDP 都能看到这些画面,从 SeaBIOS 的启动信息一直到桌面。
磁盘、网卡、鼠标:新写三个设备
三个设备都是照硬件手册和规范从头写的。QEMU 和 KVM 是 GPL 许可,我们拿它们当对照组看行为,不拷它们的代码。
| 设备 | 模拟的硬件 | 依据 | 谁需要它 |
|---|---|---|---|
| IDE 控制器 | Intel PIIX3 IDE,硬盘和 ATAPI 光驱,PIO 和 DMA | ATA/ATAPI-6、PIIX3 数据手册、Bus Master IDE 规范 | Windows XP、Server 2003 |
| 网卡 | AMD PCnet-PCI II | AMD Am79C970A 数据手册 | Windows XP、Server 2003 |
| USB 1.1 控制器 | Intel PIIX3 UHCI,上面接坐标板(tablet) | Intel UHCI 1.1 设计指南 | Windows 7、XP、Haiku |
为什么是 IDE:XP 自带驱动、SeaBIOS 能引导、接口简单,三条同时满足的只有它。XP 安装盘的光驱也要 ATAPI。网卡同理:XP 不带 e1000 的驱动,自带驱动能用的只有 PCnet。
为什么要 UHCI:Windows 7 没有 xHCI 驱动,PS/2 鼠标又只报相对位移,远程控制台里指针跟不上手。USB 坐标板报的是绝对坐标,接在 UHCI 上,老系统用自带的驱动就能认。
三个设备都支持挂起和恢复。写的过程中还修了这些:
| 现象 | 原因 | 修正 |
|---|---|---|
| 从 IDE 光驱装系统极慢,约 300 KB/s | bhyve 模拟 REP INSW 时一个字做一次地址翻译 |
数据端口一次最多搬 4096 字节,快了 18 倍 |
| Windows 枚举了 IDE 控制器但不启动它 | SeaBIOS 分配的 I/O 端口不在 ACPI 声明的窗口里 | 窗口扩到 0xFFFF |
| 不用 ACPI 的系统收不到设备中断 | bhyve 接中断引脚的方式和 SeaBIOS 按 i440fx 算出来的不同 | BIOS guest 改用 i440fx 的接法 |
Windows 7 装完后启动报 A disk read error occurred |
bhyve 把后端存储的块大小(16K)报成物理扇区,Windows 7 照它格式化出 16K 的簇,自己的引导代码读不了 | 物理扇区最多报 4K |
| Haiku 里指针不动 | Haiku 把队列头挂在另一个队列头下面,第一版 UHCI 跳过了这种情况 | 按规范进入下一层队列头 |
Windows XP:装到桌面之前的五个问题
设备都有了,XP 还是装不完。下面五个问题里,四个是原版 bhyve 和 vmm 偏离了真实硬件的行为,一个是性能。
| 现象 | 原因 | 修正 |
|---|---|---|
第一次从硬盘启动蓝屏 STOP 0x7F |
guest 屏蔽了 8259 的中断请求,vmm 却还记着“有中断”,注入时 PIC 只能给出伪中断向量 | PIC 已经没有请求时丢掉这次中断,不注入 |
| 安装程序多出一屏“自动配置” | bhyve 的 ACPI 里有 SPCR 表,XP 把机器当成了无显示器的服务器 | XP 的模板不给 SPCR 表,和 ESXi、QEMU 一样 |
| 图形安装阶段极慢,“安装设备”要 9 分半以上 | XP 每次升降中断级别都写本地 APIC 的 TPR,每次都要退出到 hypervisor 模拟,每个 vCPU 每秒约 9 万次 | 打开 APICv,由 CPU 硬件处理 TPR |
| XP 里没有网卡 | XP 的驱动往 PCI 配置空间的只读寄存器写 0。真硬件不理,bhyve 存了下来,设备的厂商 ID 变成 0 | 只读寄存器写不进去 |
| XP 重启或关机时 bhyve 崩溃 | XP 用 NMI 停掉另一个处理器,它的 NMI 是任务门;bhyve 模拟任务切换时把标志寄存器的固定位也清掉了,CPU 拒绝进入 guest | 固定位按处理器的规定保留 |
APICv 的效果最明显。同一段“安装设备”:
| 平台 | 用时 |
|---|---|
| KVM(对照) | 2 分钟 |
| bhyve,没有 APICv | 9 分半以上 |
| bhyve,打开 APICv | 2 分半 |
这也是 APICv 第一次在 keelOS 的真机上跑起来。
查这些问题靠三个办法。第一,同一张安装盘在 KVM 上用同样的虚拟硬件装一遍,比访问序列和时间。第二,把 guest 的磁盘做快照,挂出来读 XP 自己带时间戳的安装日志:进度条不动不等于卡死。第三,用开源的 ReactOS 当替身,它和 XP 同一代内核接口,能把内核日志打到串口上。
现在能跑哪些系统
下面都是在真机上实测的结果(第二台开发机,SeaBIOS,2026-09-29 到 09-30),用的都是系统自带的驱动。
| 系统 | 虚拟硬件 | 结果 |
|---|---|---|
| Windows XP Professional SP2 | IDE、PCnet、UHCI 坐标板 | 装好:桌面、网络(DHCP)、重启、关机、挂起恢复、RDP |
| Windows Server 2003 R2(日文版) | 同 XP | 装好:桌面、网络、重启、关机 |
| Windows 7 SP1 x64 | IDE 或 AHCI、UHCI 坐标板 | 装好:指针跟手,设备管理器里没有不明设备 |
| Windows 10 22H2(BIOS 引导) | AHCI、VBE 1024×768 | 显示正常,显示适配器没有代码 12 |
| Haiku R1/beta6 | AHCI、e1000、UHCI 坐标板 | 装好,从硬盘启动 |
| FreeBSD 14.5、Debian 13(BIOS 引导) | virtio-blk、AHCI、IDE | 启动到登录:FreeBSD 17 秒,Debian 6–7 秒 |
老系统同样要过 keelOS 的招牌测试:宿主系统(root)重启时 guest 不关机。我们挑了最难的时刻来测,XP 安装到一半、正在往硬盘拷文件的时候(第一台开发机,2026-09-30):
| 测试 | 结果 |
|---|---|
| 拷文件时挂起 37 秒再恢复 | 接着拷,11% 到 23% |
| 拷文件时 root 重新加载 | guest 暂停 35.8 秒,接着拷到 43% |
| 图形安装阶段挂起 32 秒再恢复 | 鼠标指针前后都能动 |
BIOS guest 的显示状态(VGA 寄存器、256K 显存、VBE 寄存器)也进了检查点:停在文字控制台的 Debian 恢复后画面原样,新的输出接着画。
在 keelOS 里建一台 XP 只要一条命令,模板里已经选好了上面这套硬件:
keel vm create --template windowsxp --alias xp --iso XP.iso --nic bridge0
还没做完的
- APICv 默认还是关的。打开它时,恢复出来的 guest 会丢电平触发的中断(XP 恢复后鼠标不动)。修正已经写好,还没有上真机测。测过、再跑完各种 guest 的回归和 root 重启,才会改成默认打开。
- BIOS guest 没有启动顺序。SeaBIOS 现在先试硬盘再试光驱,硬盘上有签名但没有引导代码时会卡住。
- VBE 模式下的挂起恢复(Windows 桌面)和 guest 停在 SeaBIOS 里时的 root 重启还没测。
- root 重启之后 XP 拷文件变慢(每秒 0.7 MB 降到 0.15–0.3 MB),原因还没查。
- ESXi 的其余虚拟硬件。LSI Logic 的 SCSI 控制器先搁置了,PVSCSI、VMXNET3、E1000e 还没做。迁移工具的第一版改用离线修改 guest 注册表的办法。
- SMBIOS 还是 SeaBIOS 自己生成的那一份,bhyve 的表没有传过去。
界线在哪里
Windows XP 是 keelOS 支持的最老的系统。DOS、Windows 3.x、Windows 9x 不在范围内:它们要软驱、声卡和更快的 VGA,这些是模拟器(DOSBox、86Box、QEMU)的长项,不该跑在硬件虚拟化的 hypervisor 上。keelOS 不放弃的,是还在干活的老系统。
结语
支持老系统,一半是补设备,一半是还债。IDE、PCnet、UHCI 是新写的;PIT 的计数 0、8259 的伪中断、只读的 PCI 寄存器、VGA 的写模式,则是原版一直就有的错,只是新系统从不走那些路径。老系统按当年硬件的实际行为写驱动,虚拟硬件差一点都不行。
修好的地方不只服务老系统:只读的 PCI 寄存器、PIT、伪中断的修正对所有 guest 生效,APICv 对新系统同样是提速。
下一步是把 APICv 改成默认打开,给 BIOS guest 加上启动顺序,然后做从 ESXi 导入虚拟机的工具。
设计、每一步的现象和测量数据见 keelOS 仓库 docs/30(从 VMware 迁移,传统 BIOS guest)。