1. 为什么要在 Apple Silicon 上折腾 Windows 10 ARM64
手里这台 M 系列芯片的 Mac,日常写代码、剪视频都够用,但总有些场景绕不开 Windows:某个只出 x86 版本的行业软件、一个老旧的串口调试工具、或者单纯想跑个只能在 Windows 下编译的工程。虚拟机方案不少,Parallels 最省心但要花钱,VMware Fusion 对 ARM 版 Windows 的支持一直不算完美,而 QEMU 这条路线虽然折腾,胜在完全免费、开源、可控,出了问题能自己钻进去查。
我前后在 M1 和 M2 两台机器上各装过几遍,踩过的坑能写满一页纸:UEFI 固件选错导致进不去安装界面、virtio 驱动没提前塞进去导致装完系统找不到硬盘、网络配置写成 user 模式结果内网互 ping 不通、还有那个经典的 TPM 报错。这篇就把整套流程从头到尾捋一遍,包括每一步为什么这么做、参数怎么算、驱动从哪来、出问题怎么排查。
先说清楚这套方案适合谁:适合愿意花一两个小时动手、能接受命令行操作、对性能没有极致要求但求稳定可用的开发者。如果你只想点两下鼠标就完事,那商业方案更合适;但如果你想搞清楚虚拟化到底在干什么,或者需要一个可以随意快照、随意改配置、不依赖任何第三方授权的研究环境,QEMU 这条路值得走。
整个方案的核心构成是四块:QEMU 8.2 本体负责虚拟化调度,UEFI 固件(edk2 的 ARM 版本)负责引导,Windows 10 ARM64 的安装镜像是系统来源,virtio 驱动包解决半虚拟化设备识别问题。四者缺一不可,任何一块版本对不上都可能卡在某个莫名其妙的界面。下面按实际动手顺序展开,先讲整体设计思路,再拆细节,最后给排查表。
2. 整体方案设计与关键选型拆解
2.1 为什么是 QEMU 而不是别的虚拟化方案
在 Apple Silicon 上跑 Windows,本质上要解决一个指令集翻译的问题。M 系列芯片是 ARM64 架构,而绝大多数 Windows 软件是 x86_64 编译的。这里有个容易混淆的点:Windows 10 ARM64 本身是 ARM64 原生系统,它内部自带 x86/x64 的模拟层(叫 CHPE 那一套),所以你在 ARM 版 Windows 里跑 x86 软件,是 Windows 自己在翻译,不是 QEMU 在翻译。QEMU 在这里的角色只是把 ARM64 的虚拟硬件呈现给 Windows,指令几乎是一对一执行的,性能损耗主要来自虚拟化开销而非指令翻译。
这就解释了为什么这条路性能其实不差。QEMU 在 ARM 主机上跑 ARM 客户机,走的是硬件加速路径(HVF,Hypervisor.framework),不是纯软件模拟(TCG)。纯 TCG 模式下跑 Windows 会慢到怀疑人生,而 HVF 加速下日常办公、编译中小型项目都是可用的。所以选型的第一条铁律:必须开启 HVF 加速,命令行里那个-accel hvf不是可选项而是必选项。
那为什么不直接用 Parallels 或 VMware?商业方案确实封装得好,驱动、共享文件夹、剪贴板全给你配好了。但它们的封闭性也带来限制:你想改个虚拟磁盘格式、想挂个特殊的 PCI 设备、想抓包看虚拟网卡的流量,往往无从下手。QEMU 的好处是所有东西都是显式的,磁盘是一个 qcow2 文件,网卡是一段命令行参数,固件是一个可替换的 fd 文件。对需要深度控制环境的人来说,这种透明性比省事更重要。
2.2 UEFI 固件为什么必须用 edk2 的 ARM 版本
Windows 10 ARM64 不接受传统 BIOS 引导,必须走 UEFI。而 UEFI 固件本身也是分架构的,x86 的 OVMF 固件在 ARM 主机上根本跑不起来。这里要用的是 edk2 项目编译出的QEMU_EFI.fd(也叫 AAVMF,ARM Architecture Virtual Machine Firmware)。它的作用是初始化虚拟硬件、提供 UEFI 运行时服务、最后把控制权交给 Windows 的引导管理器。
固件版本这块有个坑:不同来源的 fd 文件大小不一样,常见的有 64MB 和 128MB 两种。QEMU 的 pflash 设备对大小有要求,如果固件文件和 pflash 参数里的 size 对不上,启动时会直接报错或者黑屏。我的做法是先用ls -lh看清楚文件实际大小,再把这个值填进参数里,别凭记忆写。另外 edk2 有稳定版和每日构建版之分,稳定版更保险,每日构建版可能带新特性但也可能引入回归,生产用途建议锁稳定版。
2.3 磁盘格式选 qcow2 而不是 raw 的理由
qcow2 是 QEMU 的原生磁盘格式,支持写时复制、快照、稀疏分配。稀疏分配这点很实用:你创建一个 64GB 的虚拟磁盘,实际占用只有几百 MB,随着系统往里写数据才逐步增长。raw 格式虽然性能略好,但一创建就是实打实的 64GB,而且做快照要靠外部工具。
性能差距在实际使用中几乎感知不到,因为瓶颈通常在 HVF 加速和内存带宽上,不在磁盘格式的元数据开销上。所以除非你有极端的 IO 需求,qcow2 是更合理的选择。创建命令里那个preallocation=metadata参数值得加上,它预分配元数据但不预分配数据块,兼顾了创建速度和后续写入性能。
2.4 virtio 驱动为什么必须提前准备
这是新手最容易翻车的地方。QEMU 为了性能,默认给客户机呈现的是 virtio 系列半虚拟化设备:virtio-blk 磁盘、virtio-net 网卡、virtio-gpu 显卡等。这些设备效率高,但 Windows 原生不带它们的驱动。结果就是:安装程序能启动(因为引导阶段用的是固件自带的基本驱动),但一到选择安装磁盘的界面,列表空空如也,因为认不出 virtio 磁盘。
解决办法是在安装前把 virtio 驱动包挂载成一个额外的 ISO,安装到磁盘选择界面时手动加载。驱动包来自 Fedora 的 virtio-win 项目,里面有各个 Windows 版本对应的驱动。这里要注意架构匹配:要的是viostor(磁盘)和NetKVM(网卡)的 ARM64 版本,不是 x86 版本。驱动包目录结构里w10/ARM64才是我们要的,别拿错。
3. 环境准备与核心组件获取
3.1 安装 QEMU 8.2 并验证 HVF 可用
macOS 上装 QEMU 最省事的是 Homebrew。执行brew install qemu会拉到当前稳定版,写这篇时是 8.2 系列。装完先别急着往下走,做两个验证:一是qemu-system-aarch64 --version确认版本号,二是确认 HVF 加速真的可用。
验证 HVF 有个直接办法:跑一条最小化的启动命令,看它是否报HVF is not supported之类的错误。更稳妥的是查系统信息,M 系列芯片配合较新的 macOS 版本,Hypervisor.framework 是默认开启的,一般不会有问题。但如果你之前装过某些虚拟化软件改过系统扩展,可能会冲突,这时候需要排查。
注意:Homebrew 装的 QEMU 默认编译时已经带上了 HVF 支持,不需要额外编译参数。如果你是从源码自己编,configure 阶段要确保检测到了 Hypervisor.framework,否则跑起来会退回 TCG 模式,性能天差地别。
装完之后建议把 QEMU 的路径记下来,后面写启动脚本时要用绝对路径或者确保 PATH 里有它。我习惯在项目目录下放一个启动脚本,里面用$(which qemu-system-aarch64)动态取路径,换机器也不用改。
3.2 获取 UEFI 固件并核对大小
edk2 的 ARM 虚拟固件有几个获取渠道:Homebrew 的 qemu 包里其实自带了一份(在 share 目录下),也可以从 edk2 的发布页单独下。我倾向用 QEMU 自带的那份,版本匹配度最好,省得自己去找对应关系。
找到之后第一件事是看大小:
ls -lh /opt/homebrew/share/qemu/edk2-aarch64-code.fd假设输出显示是 64M,那后面 pflash 参数里就写 64M。有些教程里写 128M,那是另一份固件,混用会出问题。另外这份 fd 是只读的代码部分,实际运行时 QEMU 还需要一份可写的变量存储(vars),通常做法是把 code 复制一份当 vars 用,或者用 QEMU 提供的模板。我一般复制一份改名成vars.fd,这样每次重装系统时变量区是干净的,避免残留的启动项干扰。
3.3 Windows 10 ARM64 镜像的来源与校验
镜像这块要说明白:微软官方提供过 Windows 10 ARM64 的评估版和 Insider 版镜像,格式通常是 ISO 或者 VHDX。ISO 适合走安装流程,VHDX 是已经装好的虚拟磁盘可以直接挂载启动。对想体验完整安装过程的人来说 ISO 更合适,对只想快速用起来的人 VHDX 更省事。
拿到镜像后务必校验哈希。下载过程中断导致镜像损坏是很常见的坑,表现是安装到一半报文件校验失败或者解压错误。用shasum -a 256算一遍,和官方公布的对比。这一步花不了两分钟,能省掉后面几小时的排查。
镜像还有个细节:部分版本的 ISO 里引导文件是针对物理机 UEFI 的,在 QEMU 里可能需要额外处理。如果遇到引导失败,可以试试用 VHDX 直接启动,或者检查 ISO 的 EFI 引导分区结构是否完整。
3.4 virtio 驱动包的下载与目录确认
virtio-win 的驱动包是一个 ISO,里面按 Windows 版本和架构分了目录。下载最新稳定版后挂载查看结构,重点确认两个路径存在:viostor/w10/ARM64(磁盘驱动)和NetKVM/w10/ARM64(网卡驱动)。如果只有w10/amd64而没有 ARM64 目录,说明这个版本太老,得换新的。
驱动包不需要解压,直接作为 CD-ROM 挂给虚拟机就行,安装时在磁盘选择界面点"加载驱动程序",浏览到对应目录即可。这里有个实操技巧:把驱动 ISO 和安装 ISO 同时挂上,安装程序里能同时看到两个光驱,加载驱动时路径别选错。
4. 虚拟机创建与参数配置实操
4.1 创建虚拟磁盘并计算合理容量
磁盘容量怎么定?我的经验是:系统本身装完占 20GB 左右,加上常用软件和更新缓存,64GB 是舒适线,128GB 是宽裕线。考虑到 qcow2 是稀疏分配,创建时写大一点不占实际空间,所以直接给 64GB 起步。
qemu-img create -f qcow2 \ -o preallocation=metadata \ win10-arm64.qcow2 64Gpreallocation=metadata的作用是提前把元数据表建好,后续写入时不用频繁扩展元数据,能减少碎片。注意它不预分配数据块,所以创建瞬间完成,文件实际大小很小。如果你追求极致性能可以改成preallocation=falloc,但那会实打实占用 64GB,一般没必要。
4.2 内存与 CPU 核心数的分配逻辑
内存分配要看宿主机的总内存。M1 基础款 8GB 的话,给虚拟机 4GB 比较稳妥,留 4GB 给 macOS 本身;16GB 的机器给 8GB 很舒服;再往上给 8GB 到 12GB 都行。给太多会导致宿主机内存压力大,反而拖慢整体。
CPU 核心数同理,别把物理核心全给虚拟机。M1 是 4 性能核加 4 能效核的配置,给虚拟机 4 个 vCPU 是比较平衡的选择。QEMU 的-smp参数支持拓扑配置,简单场景写-smp 4即可,复杂场景可以指定sockets=1,cores=4,threads=1。
提示:vCPU 数量不是越多越好。Windows 对超过物理核心数的 vCPU 调度效率会下降,而且 QEMU 的每个 vCPU 是一个线程,核心给多了宿主机调度压力大。实测 4 vCPU 在 M1 上跑 Windows 10 的日常操作已经很流畅。
4.3 完整启动命令逐段解析
把前面所有组件串起来的启动命令是核心。我把它拆成几段讲,每段对应一类设备:
qemu-system-aarch64 \ -machine virt,highmem=on \ -accel hvf \ -cpu host \ -smp 4 \ -m 8192 \ -drive if=pflash,format=raw,readonly=on,file=edk2-aarch64-code.fd \ -drive if=pflash,format=raw,file=vars.fd \ -drive file=win10-arm64.qcow2,if=none,id=disk0,format=qcow2 \ -device virtio-blk-pci,drive=disk0,bootindex=1 \ -cdrom win10-arm64.iso \ -drive file=virtio-win.iso,media=cdrom,readonly=on \ -netdev user,id=net0 \ -device virtio-net-pci,netdev=net0 \ -device qemu-xhci \ -device usb-kbd \ -device usb-tablet \ -display cocoa \ -vga none \ -device virtio-gpu-pci逐段说:-machine virt是 ARM 的通用虚拟平台,highmem=on让客户机能访问高地址内存,对大内存配置必要。-accel hvf和-cpu host是性能关键,前者启用硬件加速,后者让客户机看到和宿主机同款的 CPU 特性。两个 pflash 分别挂固件的代码区和变量区,代码区只读,变量区可写。
磁盘用if=none加显式-device的方式挂载,比直接用-drive if=virtio更灵活,因为可以指定bootindex。bootindex=1表示优先从这个磁盘启动,装完系统后不用改命令就能直接进 Windows。
网络用 user 模式(NAT),这是最省事的配置,虚拟机可以访问外网,宿主机也能通过端口转发访问虚拟机的服务。缺点是虚拟机之间、虚拟机和宿主机之间的直接互访需要额外配置端口转发。如果只是上网和装软件,user 模式足够。
输入设备用 USB 键盘和 USB 平板(tablet),tablet 的好处是鼠标指针能无缝移动,不会出现"抓取"和"释放"的切换感。显示用 virtio-gpu,配合后面的驱动能获得不错的 2D 加速。
4.4 网络模式的取舍与端口转发配置
user 模式虽然简单,但有个限制:宿主机访问虚拟机的服务需要显式做端口转发。比如虚拟机里跑了个 8080 端口的 Web 服务,宿主机要访问得这样配:
-netdev user,id=net0,hostfwd=tcp::8080-:8080这样宿主机访问localhost:8080就转发到虚拟机的 8080。多个端口就写多条 hostfwd。如果需要一个虚拟机能被局域网其他机器访问,user 模式就不够了,得用 bridge 模式,但那在 macOS 上配置复杂,需要额外的网络工具,一般场景用 user 模式加端口转发就够。
注意:user 模式下虚拟机的 IP 通常是 10.0.2.15,网关是 10.0.2.2,这个网关地址在虚拟机内部可以用来访问宿主机。这个细节在调试网络时很有用。
5. 系统安装全流程与驱动加载
5.1 从固件引导到安装界面的完整过程
启动命令跑起来后,QEMU 窗口会先显示 UEFI 固件的启动画面,几秒后进入 Windows 安装程序的加载界面。如果卡在固件的 shell 界面不动,说明没找到可引导的介质,检查-cdrom参数和 ISO 是否完整。
安装程序加载时会转圈一段时间,这是正常的,ARM 版安装程序的初始化比 x86 慢一些。进入语言选择界面后一路下一步,到"现在安装"那一步点下去,接下来是关键的分区界面。
5.2 磁盘选择界面加载 virtio 驱动的操作细节
到了"你想将 Windows 安装在哪里"这个界面,正常情况下应该看到一块 64GB 的未分配空间。如果列表是空的,别慌,这正是 virtio 磁盘驱动没加载的表现。点左下角的"加载驱动程序",浏览到 virtio-win 那个光驱,进入viostor/w10/ARM64目录,选中驱动文件确认。
加载成功后列表会刷新,出现那块虚拟磁盘。这时候选中它点下一步,安装程序会自动创建分区结构。这里有个细节:安装程序可能会额外创建几个小分区(恢复分区、EFI 分区等),这是正常的,别手动去删。
提示:如果加载驱动时报"找不到签名"或者驱动安装失败,检查是不是拿错了架构的驱动。ARM64 的驱动文件名和 x86 的不一样,目录路径里明确带 ARM64 才对。
5.3 安装过程中的等待与首次启动配置
分区选好后就是复制文件和展开镜像,这个过程在 HVF 加速下大概十几到二十分钟,取决于磁盘速度和分配的 CPU 核心。期间虚拟机会自动重启几次,这是正常的,别手动干预。
首次启动进入 OOBE(开箱体验)阶段,会问你区域、键盘、网络。网络这一步如果 virtio 网卡驱动还没装,可能显示没有网络连接,可以先跳过,进系统后再装驱动。账户设置按提示走,注意 ARM 版 Windows 10 的 OOBE 流程和 x86 版基本一致。
进桌面后第一件事是装网卡驱动。打开设备管理器,会看到网络适配器那里有个带感叹号的未知设备,指向 virtio-win 光驱里的NetKVM/w10/ARM64目录更新驱动即可。装完网络就通了,可以正常上网。
5.4 装完系统后的驱动补全与优化
除了磁盘和网卡,还有几个设备可能需要驱动:virtio-gpu 显卡(在viogpudo/w10/ARM64)、气球设备(内存动态调整,在Balloon/w10/ARM64)、串口设备等。不一定全装,按需来。显卡驱动装上后显示效果会好一些,气球驱动装上后 QEMU 能根据宿主机内存压力动态调整虚拟机内存。
装完驱动建议做一次快照,把干净的系统状态存下来:
qemu-img snapshot -c clean-install win10-arm64.qcow2以后系统搞乱了,qemu-img snapshot -a clean-install一键回滚,比重装快得多。这个习惯强烈建议养成,尤其是要在这个环境里做实验的时候。
6. 常见问题排查与避坑经验实录
6.1 启动阶段黑屏或卡在固件界面
这是最高频的问题,原因通常有三类。第一类是固件文件大小和 pflash 参数不匹配,表现为 QEMU 直接报错退出或者窗口一闪而过。解决办法是核对ls -lh的实际大小,把参数改成一致。第二类是 ISO 镜像损坏,表现为固件能启动但找不到引导项,重新校验哈希、重新下载。第三类是-cpu host在某些 macOS 版本上和 HVF 配合有问题,可以试试改成-cpu max看是否能过。
排查顺序建议:先看 QEMU 的终端输出有没有报错信息,再看固件界面是否能进 shell,最后才怀疑镜像。终端输出是最直接的线索,别忽略它。
6.2 安装时找不到磁盘的完整排查路径
磁盘列表为空,按可能性排序:驱动没加载(最常见)、驱动架构不对、磁盘设备没正确挂载。先确认 virtio-win 的 ISO 确实挂上了(安装界面能看到两个光驱),再确认加载的驱动路径是 ARM64 的,最后检查启动命令里-device virtio-blk-pci那段有没有拼写错误。
有个隐蔽的坑:如果你用了-drive if=virtio的简写方式,同时又写了-device virtio-blk-pci,可能导致设备重复或者冲突。统一用if=none加显式 device 的方式最不容易出错。
6.3 网络不通的三种典型场景与对策
场景一:虚拟机完全没网。检查网卡驱动是否装了,设备管理器里有没有未知设备。场景二:虚拟机内能上网但宿主机访问不了虚拟机的服务。这是 user 模式的正常表现,需要加 hostfwd 端口转发。场景三:虚拟机之间互相 ping 不通。user 模式下每个虚拟机是独立的 NAT 网络,互相隔离,要互通得换 bridge 或 socket 模式。
6.4 性能调优的几个实测有效的手段
第一,确保-accel hvf生效,用-accel help能看到 hvf 在列表里。第二,vCPU 数量别超过物理性能核数。第三,磁盘用 qcow2 加cache=writeback能提升写入性能,但要注意断电可能丢数据,权衡使用。第四,内存给足但别超,8GB 宿主机给 4GB,16GB 给 8GB。第五,关闭不必要的虚拟设备(比如用不到的 USB 控制器),减少中断开销。
下面这张表是我整理的常见问题速查:
| 现象 | 可能原因 | 排查动作 |
|---|---|---|
| QEMU 启动即退出 | 固件大小与 pflash 参数不符 | 核对 fd 文件实际大小 |
| 卡在固件 shell | ISO 未正确挂载或损坏 | 校验哈希,检查 -cdrom |
| 安装界面无磁盘 | virtio 驱动未加载或架构错 | 加载 viostor/w10/ARM64 |
| 装完无网络 | 网卡驱动未装 | 设备管理器更新 NetKVM 驱动 |
| 宿主机访问不了虚拟机服务 | user 模式无端口转发 | 加 hostfwd 参数 |
| 运行卡顿 | 未启用 HVF 或 vCPU 过多 | 检查 -accel hvf,减少 vCPU |
6.5 快照管理与日常维护习惯
qcow2 的快照功能是这套方案的一大优势。建议在三个节点各做一次快照:干净安装完成、常用软件装好、驱动全部就位。命名清晰一点,比如clean、apps-ready、fully-driven。回滚命令是qemu-img snapshot -a 快照名 磁盘文件,注意回滚前虚拟机必须关机。
磁盘文件会随着使用不断增长,定期用qemu-img info看实际占用,如果膨胀得厉害可以用qemu-img convert做一次压缩重整,把稀疏空间回收。这个操作耗时较长,建议在空闲时做。
7. 这套方案的实际体验与后续可扩展方向
跑起来之后,日常办公、代码编辑、中小型项目编译都能胜任。x86 软件通过 Windows 自带的模拟层运行,轻量工具几乎无感,重负载的软件会有明显性能损失,这是架构决定的,任何虚拟化方案都绕不开。图形性能方面,virtio-gpu 配合驱动能应付 2D 界面,3D 加速有限,玩游戏不是这套方案的强项。
后续可以扩展的方向不少:比如把网络从 user 模式换成 bridge 模式,让虚拟机融入局域网;比如用 QEMU 的 QMP 接口做自动化管理,脚本化地开关机、做快照;再比如尝试 Windows 11 ARM64,流程大同小异但多了 TPM 的要求,需要额外挂一个 swtpm 设备。这些都可以在现有基础上逐步加,不用推倒重来。
我个人在实际操作中的体会是:这套方案的价值不在于"免费替代商业软件",而在于它把虚拟化的每一层都摊开给你看。你清楚地知道磁盘在哪、固件是什么版本、网络怎么走,出了问题能一层层往下查。这种掌控感,是封装好的商业方案给不了的。第一次装可能会花两三个小时,装顺了之后半小时能搞定一台新的,而且配置全部在脚本里,换机器复制粘贴就行。踩过的那些坑,回头看都是理解虚拟化原理的必经之路。