10 KiB
macOS 上 Windows 11 Arm 安装慢的分析与处理
记录在 Apple Silicon(M5 Max,macOS 26.4,QEMU 11.1.2,HVF)上用 ./run-windows11.py --install-from-iso
安装 Windows 11 Arm 时"安装很慢"的分析过程、验证数据和最终方案。
结论
- 慢的原因是安装介质走了模拟的 USB 光驱(
usb-storage挂 ISO):读安装文件平均只有约 4 MB/s。 - 把安装介质改成FAT32 分区里的 NVMe 磁盘镜像(
install.wim拆成.swm)后,读速达到 54 到 92 MB/s (前 7 个采样平均约 74 MB/s),安装在约 80 秒内到 40%,复制阶段约 2 分钟结束。 - 方案已接进
run-windows11.py:--install-from-img,镜像由vm-images/build-windows-install-disk.sh生成。
环境
| 项目 | 值 |
|---|---|
| 主机 | Apple M5 Max,arm64,macOS 26.4,18 核,48 GB 内存 |
| QEMU | 11.1.2,qemu-system-aarch64,加速器 HVF |
| 客户机配置 | qemu/windows11.ini + qemu/windows11-darwin-arm64.ini(virt 机型,8 vCPU,8G 内存,NVMe 系统盘,virtio-net,ramfb,usb-kbd/usb-tablet) |
| 安装 ISO | Windows11_Client_arm64_en-us_26300_9457.iso(卷标 CCCOMA_A64FRE_EN-US_DV9,install.wim 7.6 GB,boot.wim 630 MB) |
| 驱动 ISO | virtio-win.iso(含 ARM64 驱动目录,根目录只有 x64/x86 的 MSI) |
一、基线:为什么慢
用 QEMU monitor 的 info blockstats 每 15 秒采样一次,用相邻两次的差值算读写速度
(采样脚本思路:连接 qemu-monitor.sock,发送 info blockstats,解析 Read/Write 的字节数、次数和总耗时)。
USB 光驱基线(带 Cocoa 窗口,15 个采样):
| 指标 | 平均 | 最大 |
|---|---|---|
| 读安装 ISO | 4.1 MB/s(约 50 iops,每次约 64 KB) | 13.3 MB/s |
| 写系统盘 | 8.6 MB/s | 30.5 MB/s |
无头模式下的结果相近(读 3.5 MB/s,写 5.8 MB/s),所以窗口显示不是瓶颈。
排除的因素:
- CPU:QEMU 只用 29% 到 123%(主机 18 核),不是算力问题;HVF 硬件加速在工作。
- 主机磁盘与刷盘:
qemu-img bench往 qcow2 写 64K 块并逐次 flush,2000 次仅 0.31 秒;info blockstats里主机侧每次读写只有 0.02 到 0.1 毫秒,flush 次数为 0。 所以曾考虑过的cache.no-flush=on没有价值,已撤销,未提交。 - 瓶颈:虚拟机自己发出的请求很少(几十 iops),主机很快、客户机在等, 与"模拟 USB 存储吞吐低"吻合。写系统盘的速度也跟着读 ISO 的速度走。
公开资料:Parallels Desktop 在 Apple Silicon 上装 Windows 11 Arm 约 15 分钟内完成,
说明同一台机器上这个安装本来不该这么慢;Linaro 的 Windows on Arm 指南也用 usb-storage 挂 ISO,
但没有找到专门量化 USB 光驱吞吐的资料,所以"USB 是瓶颈"是本地测量得出的判断。
二、走不通的尝试
- 把 ISO 直接挂到 NVMe(
nvme,drive=...,media=cdrom):QEMU 报aio failed: Operation not permitted, Setup 提示缺少介质驱动,读不到安装文件。 - 磁盘镜像只读挂载(
readonly=on):NVMe 不会向 Windows 报告写保护,Windows 挂载 NTFS 时会尝试写入, 被 QEMU 拒绝(同样是aio failed,3000 多条),卷被判为RAW。 改成snapshot=on(对客户机可写,写入落到临时覆盖层,不改镜像文件)后,错误消失。 - NTFS 分区放
install.wim:即使 NTFS 能被识别(WINSRC,盘符C:),Setup 日志仍显示Evaluating drive [C] as media location之后Found install image at [(null)],页面停在 "Install driver to show hardware"。已试过:补全mkfs.ntfs的分区起始扇区/磁头参数(-p 3147776 -H 255 -S 63)、 把 FAT 引导分区类型从 EFI(EF00)改成普通数据(0700)以便分配盘符、把引导文件整份复制进 NTFS,均无效。 根因没有查清。UEFI 固件(EDK2)也不能直接从 NTFS 引导,所以 NTFS 方案放弃。
三、最终方案:FAT32 + 拆分的 install.swm
FAT32 单文件上限 4 GB,而 install.wim 有 7.6 GB,所以用 wimlib 拆成两个分卷
(Windows Setup 认 install.swm + install2.swm)。整个介质放在一个 FAT32 分区里:
- 镜像:10 GiB 的 raw 文件,GPT,单个 FAT32 分区(类型 0700,卷标
WINPE)。 - 内容:ISO 里除
sources/install.wim外的全部文件,加上sources/install.swm(3.98 GB)和sources/install2.swm(3.60 GB)。 - 引导:从磁盘走
efi\boot\bootaa64.efi,不经过光盘的 "Press any key to boot from CD or DVD" 提示,不需要按键。 - 以 NVMe 磁盘挂载,
snapshot=on,bootindex=2(系统盘为 1)。
制作步骤
用脚本 vm-images/build-windows-install-disk.sh,在 macOS 上直接完成,不需要 root,也不需要 Debian 虚拟机;脚本也支持 Debian(见 vm-images/README.md):
brew install wimlib dosfstools gptfdisk mtools # 一次性
vm-images/build-windows-install-disk.sh [--force] ISO [OUTPUT.img]
OUTPUT 默认是 ISO 路径把 .iso 换成 .install.img。脚本做的事:
hdiutil attach -readonly把 ISO 挂载到临时目录。wimlib-imagex split把install.wim拆成两个不超过 3800 MiB 的分卷。- 按内容大小算镜像大小(内容加 5% 再加 64 MiB),
sgdisk建 GPT 和单个 FAT32 分区(类型 0700,卷标WINPE),mkfs.fat --offset直接格式化镜像里的分区。 - 用
mmd/mcopy以字节偏移直接写入镜像(不需要挂载),复制除install.wim外的全部文件和两个.swm。
mkfs.fat 在 Homebrew 里装在 sbin,脚本会自动把 brew 的 sbin 加入 PATH。
*.img 已被 vm-images/.gitignore 忽略,不会被提交。
脚本在 macOS 上生成的镜像(8571 MiB,约 44 秒)已验证:文件和目录清单与 ISO 一致(各 1055 项,差异只是目录名末尾的 /),
boot.wim 和 bootaa64.efi 逐字节一致,两个 .swm 大小与 Debian 方案一致,
并用下面的方式启动,Setup 正常运行(约 70 秒到 37%,读安装介质 54 到 84 MB/s)。
早先的验证用的是借 Debian 虚拟机(./run-debian13.py,里面用 losetup、mkfs.vfat、wimlib)手工做的镜像,
步骤相同,结果相同。
启动方式
run-windows11.py 的 --install-from-img 用这个镜像做安装介质,隐含 --install-from-iso。镜像路径是 ISO 路径
(WIN11_VM_INSTALL_ISO 或自动查找到的 ISO)把 .iso 换成 .install.img,也就是 build-windows-install-disk.sh ISO 默认生成的位置,
此时不再读取 ISO:
./run-windows11.py --install-from-img
install_media 不再作为 usb-storage 光驱,而是:
-drive file=<镜像>,if=none,id=install_media,format=raw,snapshot=on
-device nvme,drive=install_media,serial=installer,bootindex=2
snapshot=on 的原因见上面第二节:NVMe 不报告写保护,Windows 会写它挂载的卷,只读挂载会让卷变成 RAW;
snapshot=on 让写入落到临时覆盖层,镜像文件本身不会被改动。
应答介质(CIDATA 的 seed ISO)和 VirtIO ISO 仍然走 usb-storage,它们很小,不影响速度。
镜像文件不存在时会报错并提示用 build-windows-install-disk.sh 生成。
实测
带 Cocoa 窗口,前 7 个采样(复制安装文件阶段):
| 时间 | 读安装介质 |
|---|---|
| 18 s | 54.3 MB/s |
| 35 s | 84.7 MB/s |
| 53 s | 62.6 MB/s |
| 70 s | 66.6 MB/s |
| 88 s | 80.6 MB/s |
| 105 s | 79.8 MB/s |
| 123 s | 92.2 MB/s |
- 平均约 74 MB/s,对比 USB 光驱基线的 4.1 MB/s,约快 18 倍。
- 写系统盘该阶段约 95 到 143 MB/s,对比基线 8.6 MB/s。
- 约 80 秒时 Setup 显示 "Installing Windows 11" 40%;约 140 秒后读安装介质基本结束,进入后续阶段(系统盘读写为主)。
- 同一台机器、同样的 ISO,USB 光驱方式到 49% 用了约 35 分钟。
四、副产品与已知问题
Autounattend.xml的映像序号:文件里写/IMAGE/INDEX为6(适用于 x64 的多版本 ISO), 但这个 Arm ISO 的install.wim只有 3 个映像:1 Home、2 Home Single Language、3 Pro。 实际安装能继续(应答文件里的产品密钥是 Pro 的通用密钥,Setup 似乎据此选了版本), 但序号 6 并不存在,应该核对。我没有改这个文件。- VirtIO 来宾工具:
Configure-Windows.ps1用virtio-win-guest-tools.exe(32 位 x86 程序)。 当前virtio-win.iso根目录只有virtio-win-gt-x64.msi和virtio-win-gt-x86.msi,没有 arm64 的 MSI。 上游 PR #100 为安装器加了 ARM64 支持 (virtio-win-gt-arm64.msi,含 NetKVM、viostor 等),但我没确认它在哪个发行版里发布。 arm64 的网卡是 virtio-net,驱动装不上就没有网络,后面联网下载的.bat会失败。 可选方案:换含 arm64 MSI 的 ISO,或在脚本里用pnputil从NetKVM\w11\ARM64装驱动。未验证、未修改。 - 安装脚本的架构:(已修改)
40-Install-Rustup.bat和60-Install-Qt6.bat按PROCESSOR_ARCHITECTURE选择aarch64的 rustup-init 和win64_msvc2022_arm64的 Qt。aqt 没有 Arm64 版,aqt_x64.exe仍靠模拟运行,只负责下载。 - 按键提示:用 USB 光驱(原方式)时,Arm 版 ISO 会显示 "Press any key to boot from CD or DVD",几秒内不按键会掉进 UEFI Shell。 磁盘镜像方式不经过这个提示。曾经加过自动按键逻辑,按要求已删除。
- 显卡:arm64 配置用
ramfb。virtio-gpu-pci会让画面冻结在 "Start boot option"(QEMU 固件的 virtio-gpu 输出在 Windows 引导管理器处冻结)。 -daemonize与 Cocoa:Cocoa 窗口不能和-daemonize一起用,要看界面只能前台运行。
五、后续(需要决定)
- (已完成)
run-windows11.py --install-from-img和build-windows-install-disk.sh。 - 核对
Autounattend.xml的映像序号。 - 解决 arm64 下 virtio-net 的驱动安装问题(见上)。
- 清理旧的验证产物:
vm-images/install-iso/win11-arm64-install.img(10 GiB,Debian 方案做的)和 Debian 虚拟机里的~/win11-install.img。