Files
cloudhost/windows11-arm64-install-speed.md
T
GuofangandClaude Sonnet 5.5 b26286c820 Make --install-from-img take no file; derive it from the ISO path
--install-from-img now works like --install-from-iso: the installation
media is the ISO path (WIN11_VM_INSTALL_ISO or the downloader's) with
.install.img in place of .iso, which is where
build-windows-install-disk.sh writes it by default. Drop the FILE argument
and WIN11_VM_INSTALL_IMG.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 14:24:09 +08:00

167 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`):
```bash
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:
```bash
./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](https://github.com/virtio-win/virtio-win-guest-tools-installer/pull/100) 为安装器加了 ARM64 支持
(`virtio-win-gt-arm64.msi`,含 NetKVM、viostor 等),但我没确认它在哪个发行版里发布。
arm64 的网卡是 virtio-net,驱动装不上就没有网络,后面联网下载的 `.bat` 会失败。
可选方案:换含 arm64 MSI 的 ISO,或在脚本里用 `pnputil` 从 `NetKVM\w11\ARM64` 装驱动。**未验证、未修改。**
- **x64 安装脚本**:`40-Install-Rustup.bat` 下载 `x86_64` 安装器、`60-Install-Qt6.bat` 下载 `aqt_x64.exe`,
在 Arm Windows 上靠模拟运行,装出来的是 x64 版本。
- **按键提示**:用 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`。