v86 深度调查:浏览器里跑 Windows 的原理

目录

调查对象:https://copy.sh/v86/ —— 在浏览器里运行完整 Windows 的开源项目。 本文回答四个问题:它是模拟器还是别的?原理是什么?Windows 是不是精简版?每次都要下载完整镜像吗?

一、技术本质:全系统 x86 模拟器 + 运行时 x86→WASM JIT

官方定义(GitHub README,2026-10):

“v86 emulates an x86-compatible CPU and hardware. Machine code is translated to WebAssembly modules at runtime in order to achieve decent performance.”

三层理解:

  1. 全系统模拟(full-system emulation):不是 Wine 那种“API 兼容层”,也不是容器;它模拟一整台 PC,从 BIOS、CPU、内存到磁盘控制器。
  2. CPU 是“解释 + JIT”混合:先解释执行,统计热页;热到阈值后,把整页 x86 指令翻译成 WebAssembly 模块,交给浏览器编译成本地机器码。
  3. 设备是 JavaScript/Rust 模拟:VGA、PS/2、IDE、网卡、声卡等由 JS 设备模型驱动,CPU 核在 WASM 里。

所以准确说法是:“真实 OS 镜像 + 模拟硬件 + x86→WASM JIT”,不是“没有模拟器”。

二、底层架构:x86 怎么被翻译,设备怎么模拟

1. CPU 与 JIT

  • 指令集:32 位 x86,约 Pentium 4 水平,含 SSE3;不支持 64 位、多核、部分保护模式特性、任务门、单步调试等。
  • 执行流程:
    1. 解释器先跑,记录函数调用/间接跳转入口,统计每个页的热度;
    2. 页热度超过 200,000 条指令(JIT_THRESHOLD)后,编译整页,以及最多 3 个直接可达页(MAX_PAGES=3);
    3. 生成一个大函数 + br_table 分发的 WASM 模块,用 stackifier 算法把任意控制流结构化;
    4. 模块跑完退出,主循环检测下一个模块再进入(目前不做 WASM 模块链接)。
  • 为什么这么设计:WASM 没有任意跳转、不能跨函数保留硬件寄存器、没有 mmap、模块生成慢且有内存开销。所以只能“整页编译 + 大 switch + 模块边界”。
  • 优化:
    • TLB 快路径:4MB 缓存,存物理地址、只读位、是否代码页、是否 MMIO;绝大多数访问走快路径,缺页/MMIO 走慢路径;
    • lazy flags:算术指令延迟更新标志位;
    • 条件跳转直译:cmp + setcc 合并成一次比较;
    • FPU 用 Berkeley SoftFloat:80 位浮点精确但很慢。

2. 内存与分页

  • 客户机物理内存放在 WASM 线性内存/ArrayBuffer 里;
  • 分页(页表、缺页、只读页、代码页失效)完全软件模拟,没有硬件 mmap;
  • 代码页被写入时要失效对应 JIT 代码,保证自修改代码正确。

3. 设备模型

设备模拟方式说明
VGABochs VBE / SVGA,帧缓冲 + Canvas2D framebuffer,无 3D GPU 加速;默认 640×480×4bit(16 色)
键盘/鼠标8042 PS/2 控制器扫描码注入;鼠标绝对/相对定位
硬盘IDE(PATA)不是 AHCI/SATA;支持 raw .img,异步按块加载
CD-ROMIDE + 内置 ISO 9660 生成器支持 Joliet;.iso
软盘8272A 控制器1.44MB 等
网卡NE2000 (RTL8390) 或 virtio-net需要代理后端:inbrowser / wsproxy / wisp / fetch;不是裸 socket
声卡SoundBlaster 16Web Audio 输出,有延迟
其他PIT、PIC、RTC、PCI、部分 APIC、Hayes ModemACPI 仅部分/实验性

4. 为什么能“接近原生”?

准确说:不是接近原生,而是“热代码接近原生”。

  • x86 热页被翻译成 WASM,浏览器再用 Liftoff/TurboFan 编译成本地机器码,所以纯计算循环可以跑到原生的一个可观比例;
  • 但 CPU 状态在内存里而不是寄存器、模块边界、页表模拟、设备 I/O、Canvas 渲染、SoftFloat 都会拖慢;
  • 第三方实测/估算:计算密集型约为原生的 20–40%,整体比原生虚拟机慢 3–10 倍(Pi Stack 2026-06 对比文;非官方数字)。
  • Windows 98 之所以“感觉能接受”,是因为 1998 年的软件本身很轻、默认 16 色低分辨率,再加上状态快照直接跳过开机,不是因为它真的有原生速度。
  • 3D 游戏、视频解码、现代浏览器基本不要想。

三、里面的 Windows 到底是什么

1. 版本与镜像大小(关键:页面上的“13+MB”不是磁盘大小)

系统磁盘镜像内存状态快照页面显示“Size”
Windows 98300 MB raw .img128 MB13.4 MB .bin.zst13+ MB(是快照,不是磁盘)
Windows 20002 GB raw .img512 MB29.6 MB .bin.zst28+ MB(快照)
Windows 95450 MB64 MB无(或另配)19+ MB
Windows ME1 GB256 MB有28+ MB

依据:src/browser/main.js 的 profile 配置;i.copy.sh 的 Content-Length 实测(2026-10-30)。

所以“Windows 98 只有 13MB”是误解:13MB 是预启动 RAM/CPU 快照,真正的磁盘是 300MB。

2. 是 9x 还是 XP/2000?

  • 默认演示同时有 Windows 98(9x 内核) 和 Windows 2000(NT 5.0),不是二选一;
  • 还有 95、ME、NT 3.1/3.51/4.0;
  • Windows XP 不在默认列表,README 说“在特定条件下可运行”,属于实验性支持。

3. 是不是“精简定制版”?

  • 是预制好的完整安装镜像,但做了“为 v86 适配”的精简,不是微软官方的“Lite 发行版”。
  • 具体手段(来自官方 docs/windows-9x.md / windows-nt.md):
    • 用 QEMU 安装,-M pc,acpi=off,改成 Standard PC(2000 以上必须关 ACPI);
    • 最小化安装,删掉不必要组件/文件夹(如 ADMIN、DEMOS、HELP 等);
    • 装 VBE9x / VMDisp9x 驱动,才能上真彩色;默认只有 640×480×4bit;
    • 预装 IE、小游戏、Winamp、Delphi 等,纯粹为了演示。
  • Windows 98 是 9x 内核,Windows 2000 是 NT 5.0;两者不是“精简版 XP”。

4. 镜像怎么“塞进去”的?

不是运行时安装,是预制的 raw 磁盘镜像。

流程:

  1. QEMU 里正常安装 Windows;
  2. 导出 raw .img;
  3. 用 tools/split-image.py 按 256KB 切成 0-262144.img、262144-524288.img …;
  4. 静态服务器托管,v86 按需取块。

你也可以自己构建:qemu-img create -f raw hdd.img 300M → 安装 → 导出 → 切块 → 在 V86 配置里指向你的 hda。官方文档给了 9x/NT/Linux 的完整步骤。

版权提醒:v86 代码是开源 BSD-2-Clause,但 Windows 镜像版权归微软;自建/自托管镜像要自己解决授权。

四、加载机制:每次打开都要下完整镜像吗?

不需要。

1. 默认路径(?profile=windows98)

  1. 下载 JS/WASM(v86_all.js、v86.wasm)和 BIOS;
  2. 下载 13.4MB 的 zstd 状态快照(windows98_state-v2.bin.zst),浏览器解压后 restore_state(),直接进入桌面,不经历 BIOS/开机;
  3. 磁盘镜像 300MB 不整包下载:async: true + use_parts: true + fixed_chunk_size: 256KB,v86 只取客户机实际读到的 256KB 块(例如打开 IE 才取 IE 相关块);
  4. 块有内存缓存(block_cache),同一块不重复请求;服务器返回的 state 带 Cache-Control: public, max-age=43200(12 小时),浏览器 HTTP 缓存可复用。

2. 有没有 IndexedDB?

没有。 我在源码里 grep 过 indexedDB,v86 默认实现只有:

  • HTTP 缓存(服务器头控制);
  • 内存块缓存(Map,页面关闭即失);
  • 手动“Save State”是下载文件,不是自动 IndexedDB。

所以:刷新页面、缓存未过期 → 可能命中 HTTP 缓存;换浏览器/清缓存 → 重新下 13MB + 工作集。不会每次下 300MB,但也不是“永久本地缓存”。

3. 首次加载多久?

  • 状态快照 13.4MB:10–50 Mbps 下约 2–10 秒;加上 JS/WASM 和浏览器解压,桌面通常 数秒内出现;
  • 冷启动 profile(windows98-boot,无快照):要读大量磁盘块,30 秒到几分钟,取决于网络和磁盘;
  • Windows 2000 状态 29.6MB,首次更久。

以上是量级估算,不是官方 benchmark。

五、项目本身

项目事实
作者Fabian Hemmer(GitHub copy,copy.sh,copy@copy.sh)
起始2013-11-06 创建仓库
协议BSD-2-Clause(内置 SeaBIOS 等第三方组件各自许可)
Star23,644(2026-10-30)
Fork1,938
活跃度最后 push 2026-10-18;demo 构建 1e4f43c95(2026-07-03)
语言JavaScript + Rust(CPU/JIT 编译到 WASM)
支持系统Linux(32 位)、Windows 1.x/3.x/95/98/ME/NT/2000、FreeDOS/MS-DOS、ReactOS、Haiku、FreeBSD/OpenBSD、Android-x86 1.6/4.4、KolibriOS、QNX、SerenityOS 等 50+
嵌入libv86.js / npm v86;给 screen_container + BIOS + 磁盘即可
自建镜像可以;官方有 9x/NT/Linux 构建文档
主要限制无 64 位、无多核、无 3D GPU、无 USB、IDE 非 AHCI、ACPI 部分、FPU 慢、网络需代理、音频延迟、XP 实验性
镜像版权代码开源 ≠ Windows 镜像可自由分发

六、同类项目对比

项目技术路线能跑什么性能/特点
v86全系统 x86 模拟 + 运行时 x86→WASM JITWindows 9x/2000、Linux、DOS、ReactOS计算 20–40% 原生;完整 PC,兼容性好
JSLinux(Fabrice Bellard)纯 JavaScript x86 模拟(无 WASM JIT)Linux(2.6/4.x 等)更慢;只做 Linux,不支持 Windows
WebVM / CheerpX专有 x86→WASM JIT,用户态 LinuxDebian/Linux 应用接近原生;闭源、偏应用层
PCjs周期精确的 8086/286/386 模拟早期 IBM PC、DOS历史还原度高,速度慢,不适合 Win9x
QEMU.wasm / container2wasmQEMU 全系统模拟编译到 WASM多种 OS功能全但体积大、速度慢
FigmaC++ 编译到 WebAssembly(不是 x86 模拟)设计工具渲染说明 WASM 对计算密集代码可接近原生,但不是同一类
98.js用 JS 重画 Windows 98 桌面网页版“像 Win98”不是真 OS,不能跑任意 exe
wine-assemblyx86 解释器 + Win32 API 重实现直接跑 Win98 exe不启动完整 OS,应用级方案

一句话区分:v86 是“把整台 PC 搬进浏览器”;JSLinux 是“只把 Linux 搬进去”;CheerpX 是“把 Linux 用户态搬进去但闭源”;98.js 是“画了一个 Windows 98”;Figma 是“用 WASM 加速自己的 C++”,不是模拟器。

七、事实 / 推测对照

说法定性
v86 是全系统 x86 模拟器,不是 OS 重实现事实(README/源码)
运行时把 x86 热页翻译成 WASM事实(docs/how-it-works.md、jit.rs)
Windows 98 磁盘 300MB、2000 磁盘 2GB事实(profile 配置)
页面“13+MB/28+MB”是状态快照,不是磁盘事实(HEAD 实测 13.4MB/29.6MB)
每次打开要下完整镜像错误;只下状态 + 按需 256KB 块
用 IndexedDB 缓存镜像没有;默认 HTTP 缓存 + 内存块缓存(源码 grep 无 IndexedDB)
Windows 98 是 9x、2000 是 NT 5.0事实;XP 非默认,实验性
镜像是“精简定制版”部分事实:完整安装后做兼容性精简(Standard PC、关 ACPI、VBE 驱动、删组件),非官方 Lite 发行版
“接近原生速度”夸大;第三方估算计算 20–40%,整体慢 3–10 倍
作者 Fabian Hemmer、BSD-2-Clause、23.6k stars事实(GitHub API 2026-10-30)
自建镜像可塞进去事实(官方 9x/NT 文档、split-image.py)
Windows 镜像可自由分发错误;代码开源不等于镜像授权

八、结论

  • 它是真模拟器,跑的是真 OS:真实 Windows 镜像 + 模拟的 x86 PC + x86→WASM JIT。
  • “接近原生”要打引号:热代码可以到原生的 20–40%,整体远不到原生;Windows 98 的流畅感来自“轻负载 + 快照跳过开机”。
  • 13MB 不是 Windows 的大小:是预启动状态快照;磁盘是 300MB(98)/2GB(2000)。
  • 不用每次下完整镜像:状态快照 + 256KB 按需分块 + HTTP 缓存;没有 IndexedDB。
  • 项目健康:23.6k stars、BSD-2-Clause、2026 年仍活跃;适合教学、怀旧、嵌入式 Web VM,不适合跑现代 3D/视频/生产负载。

如果陛下想,我可以下一步做两件事之一:① 在本机用 v86 嵌一个最小 Linux 镜像,实测 MIPS 和首屏时间;② 把 Windows 98 的磁盘块请求抓一遍,画出“打开 IE 到底下载了哪些块”的图。