Files
cloudhost/windows11-arm64-install-speed.md
T
2026-10-18 15:42:35 +08:00

10 KiB
Raw Blame History

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 是瓶颈"是本地测量得出的判断。

二、走不通的尝试

  1. 把 ISO 直接挂到 NVMe(nvme,drive=...,media=cdrom):QEMU 报 aio failed: Operation not permitted, Setup 提示缺少介质驱动,读不到安装文件。
  2. 磁盘镜像只读挂载(readonly=on):NVMe 不会向 Windows 报告写保护,Windows 挂载 NTFS 时会尝试写入, 被 QEMU 拒绝(同样是 aio failed,3000 多条),卷被判为 RAW。 改成 snapshot=on(对客户机可写,写入落到临时覆盖层,不改镜像文件)后,错误消失。
  3. 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。脚本做的事:

  1. hdiutil attach -readonly 把 ISO 挂载到临时目录。
  2. wimlib-imagex split 把 install.wim 拆成两个不超过 3800 MiB 的分卷。
  3. 按内容大小算镜像大小(内容加 5% 再加 64 MiB),sgdisk 建 GPT 和单个 FAT32 分区(类型 0700,卷标 WINPE), mkfs.fat --offset 直接格式化镜像里的分区。
  4. 用 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 一起用,要看界面只能前台运行。

五、后续(需要决定)

  1. (已完成)run-windows11.py --install-from-img 和 build-windows-install-disk.sh。
  2. 核对 Autounattend.xml 的映像序号。
  3. 解决 arm64 下 virtio-net 的驱动安装问题(见上)。
  4. 清理旧的验证产物:vm-images/install-iso/win11-arm64-install.img(10 GiB,Debian 方案做的)和 Debian 虚拟机里的 ~/win11-install.img。