简陋的 Win on Linux 兼容方案

本文原先希望以教程形式展开,现在看来细节是讲不完了。打算把其中一些重要的内容提一下就完事。

几乎每个桌面 Linux 用户都有运行 Windows 程序的需求,对此的解决方案也五花八门。本文所述方案以稳定易用为首要目标:

与其他方案对比(点击展开)

对比 Wine:

  • Wine 性能几乎无损失;本方案图形性能较弱。
  • 本方案较为通用,可在各种硬件、内核、发行版下运行;Wine 对环境要求较多。
  • 本方案几乎无需维护,一劳永逸;Wine 有时需为特定应用作出调整,存在紧急情况下新应用无法运行的尴尬。

对比其他虚拟机:

  • VitrualBox 图形性能略好,需额外加载内核模块,可能在内核升级后崩溃;本方案使用 KVM,无需额外内核模块,更稳定。
  • ESXi 综合性能较好,安装繁琐,收费;本方案安装简便,除 Windows 本身外完全开源。
  • 本方案中 QCOW2 磁盘镜像支持 ZSTD 压缩,可大幅减小体积(当前为 1.33 GiB)。

成品

下载链接: OneDrive | 123 云盘 | 百度网盘 。若无特殊需求,建议直接使用成品镜像。其中 win10_xxx.qcow2 是磁盘镜像主体,文件名后半段为 CRC32 值。win10_share.iso 是外置镜像,包含 WebDav 服务端。

安装 QEMU KVM:

# Fedora
sudo dnf install qemu-kvm qemu-img
# Ubuntu
sudo apt install qemu-kvm qemu-utils

接下来写入并运行 ./win10(.sh)

#!/bin/sh
base_img=win10_xxx.qcow2
snapshot_img=win10_snapshot.qcow2 # 可以考虑放进 /tmp
share_img=win10_share.iso
[ -f $snapshot_img ] || qemu-img create -q -F qcow2 -b $base_img -f qcow2 $snapshot_img
# 使用 sudo,规避鼠标捕获异常等问题
sudo qemu-kvm \
  -machine q35 -device qemu-xhci -device usb-tablet -cpu host,hv-relaxed,hv-vapic,hv-spinlocks=0x1fff,hv-vpindex,hv-time,hv-synic,hv-stimer \
  -smp 2 -m 1.5G \
  -hda $snapshot_img -cdrom $share_img \
  -nic hostfwd=tcp:127.0.0.1:9121-:5000
# 若需要更高分辨率
# -vga qxl
# 若需要音频支持
# -audiodev pa,id=pa,server=unix:${XDG_RUNTIME_DIR}/pulse/native -device usb-audio,audiodev=pa

启动后使用任意 WebDAV 客户端连接 dav://127.0.0.1:9121/,实现文件共享。nautilus 内置的那个就行,左侧栏 Other Locations > 底部 Connect to Server。

详细信息(点击展开)
  • 集成常用运行库和精简版输入法。自定义内容建议写入外置镜像。
  • 未激活,个人认为不影响使用。
  • 默认 Administrator 用户,禁用几乎所有安全功能,强制自动登录。
  • 在 QEMU 6.2 上制作,经测试 7.1 可用,之后应该也没问题。
  • 如何修改 DPI 和屏幕分辨率:外置镜像中有示例代码。
  • 为什么使用中文版系统:许多国内软件在非中文环境下会卡 Bug。也许可以换用 Tiny10。
  • 为什么不使用 virt-manager:本文同样适用,只是个人认为直接使用 QEMU 更便捷可控。

磁盘镜像更新日志:

  • 20221002(a58fd75e):进一步提升 ZSTD 压缩等级,减小体积。
  • 20221001(8b8db6b9):统一资源管理器列布局;消除开始菜单最近添加提示;恢复系统属性信息到原版。
  • 20220904(d936163a):修复输入法无法输入全角符号(原帖 84 楼);消除 IE 主页破坏提示。

鸣谢:

减小虚拟机磁盘镜像体积

当前网络上对减小镜像体积有许多过时甚至错误的内容,所以在这说道两句。下述内容中如非注明,都是指装有 Windows 系统的虚拟机磁盘镜像。

收缩与压缩

文件系统的设计非常复杂(正经点,别拿 tmpfs 说事)。删除文件,磁盘上会留下 hole;打开多个 fd 同时写入,可能产生碎片;为减少碎片,文件系统会用各种姿势做 prealloc;为提升性能,还可能缓存写入队列,先规划一下再 commit...

磁盘镜像文件本质是一个 user space 的文件系统 backend,把原本直接写入硬盘的操作转化为写入镜像文件。而客户机内文件系统与宿主机上磁盘镜像驱动之间的信息差,会导致各种问题:空洞难以被积极地回收,碎片与 prealloc 过度占用之间的权衡等。所以我们需要偶尔执行收缩、压缩这类高开销操作。

请一定区分“收缩(shrink)”与“压缩(compress)”。“收缩”是整理镜像,消除空洞、碎片、冗余数据;“压缩”则是将数据使用压缩算法进行处理。

许多教程推荐用 Virtual Disk Precompactor 或 dd 撑满分区,迫使客户机文件系统腾出连续空间,但这类做法实际上非常糟糕:需写入大量数据,耗时长;不能同时 defrag,效果差。收缩镜像最简单有效的方式是直接取出内容,写入到新镜像中。我们可以使用 Dism++ 等工具备份系统分区,还原到新镜像上:

qemu-img create -f qcow2 win10.qcow2 32G
# > 此时安装系统
mv win10.qcow2 win10.old.qcow2
qemu-img create -f qcow2 win10.qcow2 32G
# > 此时启动到 PE,拆分原镜像分区,备份到拆出的空闲分区,还原到新镜像的分区
rm win10.old.qcow2
# 可选,差异不明显。如果你接下来还要压缩,那么这一步是**完全多余**的
# qemu-img convert -p -f qcow2 -O qcow2 -c win10.qcow2 win10-shrink.qcow2

对于压缩,若你在客户机内使用 NTFS 或 Btrfs 透明压缩,就会发现这毫无作用,因为它们会尽量减少搬运,导致压缩后数据分布依然是稀疏的。个人认为目前最佳方案是在 QCOW2 上启用 ZSTD 压缩:

qemu-img convert -p -f qcow2 -O qcow2 -c -o compression_type=zstd,preallocation=off win10.qcow2 win10-zstd.qcow2

相比于 NTFS 压缩,QCOW2 的压缩解压完全在宿主机上运行,性能损失较小,且 ZSTD 算法很好地平衡了速度与体积。但这仅能压缩镜像已有内容,对新写入的数据依旧不作压缩。另外,QEMU 默认的 ZSTD 压缩配置是默认等级。由于此镜像只需读取无需写入,所以可以 修改 QCOW2 镜像的 ZSTD 压缩级别,尽力压到最小。

快照

与 Linux 不同,Windows 的可控性向来都比较糟糕。如果你有幸打开 UWF,就会看到各个进程在各种角落不断写入临时文件。我们不可能找到所有这些地方并定时清理。

使用 Snapshot 是比较好的方案:随时回滚,遏制镜像文件体积膨胀,在出现意外时兜底。

qemu-img create -q -F qcow2 -b win10_xxx.qcow2 -f qcow2 win10_snapshot.qcow2
# qemu-kvm -hda win10_snapshot.qcow2 -cpu host -accel kvm -smp 2 -m 2G

你还可以创建共用同一 Backing 的多个 Snapshot。在一段时间后若 Snapshot 体积增大,按照上文的方法进行压缩即可。

后续内容

你可以在 本文源码 的末尾找到还未发布的内容,可能包含一些实用的技巧和命令范例。