
1. 为什么要在 Windows 上跑 ARM 版 Ubuntu1.1 一个真实的需求场景手里一台 Windows 主力机日常开发环境是 x86_64但项目要交付到 ARM 服务器或者树莓派集群上。代码在本地跑得好好的一上 ARM 板子就出问题——字节序、指针长度、SIMD 指令集、依赖库的编译选项随便哪个环节都能让你调半天。最笨的办法是买一块开发板或者租一台 ARM 云主机但前者要等快递、要接线、要折腾串口后者要花钱、要联网、有延迟。最省事的方案其实是在本地 Windows 上直接跑一个 ARM 架构的 Ubuntu 虚拟机编译、调试、验证一条龙不用离开自己的工位。这个需求听起来有点绕Windows 是 x86 架构ARM 是另一套指令集怎么在 x86 上跑 ARM 的虚拟机答案是全系统模拟Full System Emulation。QEMU 这个工具能在 x86 主机上模拟出一整套 ARM 硬件环境包括 CPU、内存、磁盘控制器、网卡然后在上面安装一个完整的 ARM 版 Ubuntu 系统。跑起来之后你在虚拟机里执行uname -m会看到aarch64编译出来的二进制文件就是 ARM 架构的可以直接拷到目标设备上运行。1.2 和交叉编译比优势在哪有人会问交叉编译不也能在 x86 上生成 ARM 可执行文件吗为什么还要费劲跑一个完整的 ARM 虚拟机交叉编译确实快但它有个致命问题只能编译不能运行。你编译出来的东西对不对、跑起来会不会崩、依赖的库版本是否匹配这些都要到目标设备上才能验证。而全系统模拟的 ARM 虚拟机可以让你在本地直接运行 ARM 二进制配合 GDB 还能单步调试。对于复杂项目这种“编译-运行-调试”闭环的价值远大于交叉编译省下来的那点时间。另一个场景是依赖管理。交叉编译需要准备一套 ARM 的 sysroot里面要有目标系统的头文件和库文件版本必须和目标设备完全一致否则链接阶段就会报一堆找不到符号的错误。而在 ARM 虚拟机里你直接用apt install装依赖装出来的就是 ARM 版本和真实设备上的环境几乎一样省去了手动维护 sysroot 的麻烦。1.3 方案选型QEMU 还是别的在 Windows 上做 ARM 全系统模拟可选方案其实不多。VMware 和 VirtualBox 都是 x86 虚拟化不支持跨架构模拟。Windows 自带的 Hyper-V 也是同理。真正能用的就是 QEMU。QEMU 有两种模式用户模式User Mode和系统模式System Mode。用户模式只能模拟单个进程比如你可以在 x86 上直接运行一个 ARM 的二进制文件但它依赖的系统调用和库还是宿主机的不适合做完整的系统验证。系统模式才是我们需要的它模拟整个硬件平台从 CPU 到外设一应俱全可以安装完整的操作系统。QEMU 在 Windows 上的安装方式有好几种官方提供的 Windows 安装包、MSYS2 里通过包管理器安装、WSL2 里安装 Linux 版 QEMU。我推荐直接用官方 Windows 安装包原因是它自带 UEFI 固件和必要的驱动文件省去了手动配置的麻烦。MSYS2 的方式虽然灵活但路径和环境变量容易出问题。WSL2 的方式性能更好但网络配置会复杂一些后面会详细说。注意QEMU 的 Windows 版本在性能上比 Linux 版本略差因为缺少 KVM 这样的硬件加速支持。但做 ARM 模拟时硬件加速本来就用不上KVM 只能加速同架构虚拟化所以这个差异可以忽略。2. 环境准备与工具链搭建2.1 QEMU 的下载与安装去 QEMU 官网的下载页面找到 Windows 版本的安装包。文件名通常类似qemu-w64-setup-20240423.exe版本号会随时间更新。下载完成后直接双击安装安装路径建议保持默认的C:\Program Files\qemu避免路径中有空格或中文导致后续命令出错。安装过程中有一个选项需要注意是否安装 TAP 网络驱动。这个驱动用于给虚拟机提供桥接网络如果你需要虚拟机拥有独立的 IP 地址、能被局域网内其他设备访问就勾选上。如果只是本地开发调试用 QEMU 默认的 NAT 网络就够了可以不装。安装完成后打开 PowerShell 或 CMD输入qemu-system-aarch64 --version如果能看到版本号输出说明安装成功。如果提示“不是内部或外部命令”说明 QEMU 的安装路径没有加到系统 PATH 环境变量里。手动添加一下右键“此电脑” → 属性 → 高级系统设置 → 环境变量 → 在“系统变量”里找到 Path → 编辑 → 新建 → 填入C:\Program Files\qemu→ 确定。2.2 需要的文件清单跑一个 ARM 版 Ubuntu 虚拟机除了 QEMU 本身还需要以下几类文件UEFI 固件ARM 架构的虚拟机需要 UEFI 引导固件。QEMU 安装包里自带了一个QEMU_EFI.fd文件通常在安装目录的share子目录下。这个文件负责初始化虚拟硬件并引导操作系统。Ubuntu ARM 镜像去 Ubuntu 官网的 CD 镜像页面找到ubuntu-22.04.4-live-server-arm64.iso这样的文件。注意要选arm64版本不是 amd64。Server 版比 Desktop 版更适合虚拟机场景因为它没有图形界面资源占用小启动快。如果你确实需要图形界面也可以选 Desktop 版但模拟性能会明显下降。虚拟磁盘文件用一个空文件作为虚拟机的硬盘。QEMU 支持 qcow2 和 raw 两种格式。qcow2 支持快照和动态扩容推荐使用。创建命令后面会详细说。文件类型推荐选择作用UEFI 固件QEMU 自带 QEMU_EFI.fd引导虚拟机启动系统镜像ubuntu-22.04-server-arm64.iso安装操作系统虚拟磁盘qcow2 格式20GB 起步作为系统盘2.3 创建虚拟磁盘虚拟磁盘不需要预先分配全部空间qcow2 格式支持按需增长。创建一个 32GB 的磁盘qemu-img create -f qcow2 ubuntu-arm64.qcow2 32G这条命令会在当前目录下生成一个ubuntu-arm64.qcow2文件初始大小只有几百 KB随着虚拟机写入数据逐渐增大最大不超过 32GB。32GB 对于开发环境来说够用了如果你要编译大型项目或者装很多依赖可以给到 64GB。实操心得qcow2 文件在 Windows 上会随着使用不断增大但删除文件后空间不会自动回收。如果发现磁盘文件膨胀得太大可以用qemu-img convert命令做一次压缩把未使用的块回收掉。2.4 网络方案的取舍QEMU 在 Windows 上支持两种网络模式用户模式网络User Mode Networking和TAP 桥接网络。用户模式网络是默认配置虚拟机通过 QEMU 内置的 NAT 访问外网不需要额外配置。缺点是虚拟机没有独立 IP局域网内其他设备无法直接访问它端口映射需要手动配置。对于大多数开发场景这已经够用了。TAP 桥接网络需要安装 TAP-Windows 驱动然后把虚拟网卡和物理网卡桥接起来。配置完成后虚拟机会像一台独立设备一样出现在局域网中有自己的 IP 地址。如果你需要从其他机器 SSH 到虚拟机或者虚拟机要作为服务器被访问就需要用这种模式。我个人的建议是先用用户模式网络把系统跑起来确认一切正常后再考虑是否需要桥接。很多问题都是在桥接配置阶段出现的比如驱动签名、网卡绑定、IP 冲突先把系统跑通再折腾网络能省不少时间。3. 安装 ARM 版 Ubuntu 全流程3.1 启动安装程序所有文件准备好之后用一条命令启动虚拟机并进入安装界面qemu-system-aarch64 ^ -m 4096 ^ -cpu cortex-a72 ^ -smp 4 ^ -M virt ^ -bios QEMU_EFI.fd ^ -device virtio-gpu-pci ^ -display default ^ -device qemu-xhci ^ -device usb-kbd ^ -device usb-tablet ^ -drive ifvirtio,formatqcow2,fileubuntu-arm64.qcow2 ^ -drive ifvirtio,formatraw,fileubuntu-22.04.4-live-server-arm64.iso,readonlyon ^ -netdev user,idnet0 ^ -device virtio-net-pci,netdevnet0逐段解释这些参数的含义-m 4096分配 4GB 内存。ARM 模拟比较吃内存2GB 会卡得很难受4GB 是起步8GB 更流畅。-cpu cortex-a72模拟 Cortex-A72 处理器。这是 ARMv8 架构中比较常见的一款核心兼容性好。如果你想模拟更新的核心可以换成cortex-a76或neoverse-n1但需要确认 QEMU 版本支持。-smp 4分配 4 个虚拟 CPU 核心。注意这是模拟出来的核心性能远不如物理核心但多给几个核心能改善编译时的体验。-M virt使用 QEMU 的通用虚拟平台。这个平台不绑定具体硬件由 QEMU 动态生成设备树兼容性最好。-bios QEMU_EFI.fd指定 UEFI 固件文件。如果这个文件不在当前目录需要写完整路径。-device virtio-gpu-pci和-display default提供显示输出。Server 版安装过程中有文本界面需要显示输出才能操作。-device qemu-xhci、-device usb-kbd、-device usb-tablet分别提供 USB 控制器、键盘和鼠标输入。usb-tablet比usb-mouse的指针定位更准确。-drive ifvirtio,formatqcow2,fileubuntu-arm64.qcow2挂载虚拟磁盘作为系统盘。ifvirtio表示使用 virtio 驱动性能比 IDE 或 SCSI 模拟好很多。-drive ifvirtio,formatraw,fileubuntu-22.04.4-live-server-arm64.iso,readonlyon挂载安装镜像作为光驱。readonlyon确保镜像不会被意外写入。-netdev user,idnet0和-device virtio-net-pci,netdevnet0配置用户模式网络。3.2 安装过程中的关键选择虚拟机启动后会进入 UEFI 引导界面稍等片刻就能看到 Ubuntu 的安装菜单。选择“Install Ubuntu Server”开始安装。安装过程中有几个地方需要留意分区方案对于虚拟机场景直接用“Use an entire disk”自动分区就行不需要手动折腾 LVM 或加密。自动分区会创建一个 EFI 系统分区和一个根分区简单够用。软件包选择安装程序会问你要不要安装一些额外的软件包。OpenSSH server 一定要勾选这样装完之后可以直接从宿主机 SSH 进去不用在 QEMU 窗口里操作复制粘贴命令方便很多。其他的像 Docker、LAMP 之类的按需选择不装也可以后面手动装。用户名和密码设置一个简单的用户名和密码比如ubuntu/ubuntu。这是本地虚拟机不需要考虑安全性。安装过程大约需要 15 到 30 分钟取决于宿主机性能和分配的 CPU 核心数。模拟 ARM 指令比原生执行慢很多耐心等待。3.3 安装后的首次启动安装完成后安装程序会提示重启。重启时记得把 ISO 镜像从启动项里移除否则又会从安装镜像启动。最简单的做法是关掉虚拟机然后去掉命令中的-drive那一行挂载 ISO 的那行重新启动qemu-system-aarch64 ^ -m 4096 ^ -cpu cortex-a72 ^ -smp 4 ^ -M virt ^ -bios QEMU_EFI.fd ^ -device virtio-gpu-pci ^ -display default ^ -device qemu-xhci ^ -device usb-kbd ^ -device usb-tablet ^ -drive ifvirtio,formatqcow2,fileubuntu-arm64.qcow2 ^ -netdev user,idnet0,hostfwdtcp::2222-:22 ^ -device virtio-net-pci,netdevnet0注意这里加了一个hostfwdtcp::2222-:22意思是把宿主机的 2222 端口转发到虚拟机的 22 端口。这样你就可以在 Windows 上用 SSH 连接虚拟机ssh -p 2222 ubuntulocalhost登录进去之后执行uname -m如果输出aarch64说明 ARM 环境已经跑起来了。3.4 系统初始化配置首次登录后先做几件基础配置更新软件源sudo apt update sudo apt upgrade -y安装常用工具sudo apt install -y build-essential git curl wget vim htop配置 SSH 免密登录可选但强烈推荐把宿主机的公钥复制到虚拟机的~/.ssh/authorized_keys里之后 SSH 就不用每次输密码了。检查 CPU 信息lscpu你会看到Architecture: aarch64CPU(s): 4Model name: Cortex-A72。这些信息确认了虚拟机的 ARM 环境正常工作。注意事项在 ARM 虚拟机里编译大型项目时建议用make -j4或者make -j$(nproc)来并行编译充分利用分配的 4 个核心。但要注意内存消耗4GB 内存跑 4 个编译进程可能会 OOM如果遇到编译进程被 kill 的情况减少并行数或者增加内存分配。4. 性能调优与日常使用技巧4.1 让模拟跑得更快QEMU 的全系统模拟性能取决于几个因素CPU 核心数、内存大小、磁盘 I/O、以及是否使用了半虚拟化驱动。CPU 核心数-smp参数控制虚拟 CPU 数量。理论上越多越好但超过宿主机物理核心数后收益递减。我的经验是给到宿主机物理核心数的一半左右比较合适。比如 8 核的机器给 4 核16 核的给 8 核。内存大小-m参数控制内存。ARM 模拟对内存的需求比 x86 虚拟化高因为 QEMU 需要维护指令翻译缓存。4GB 是底线8GB 比较舒服16GB 可以跑大型编译任务。磁盘 I/O使用ifvirtio的 virtio 磁盘驱动比 IDE 或 SCSI 模拟快很多。另外把 qcow2 文件放在 SSD 上比放在机械硬盘上快一个数量级。半虚拟化驱动virtio 系列设备virtio-gpu、virtio-net、virtio-blk都是半虚拟化驱动它们通过共享内存和宿主机通信比模拟真实硬件效率高得多。能用 virtio 的地方尽量用 virtio。CPU 类型-cpu max可以让 QEMU 模拟所有支持的 ARM 特性但兼容性可能有问题。-cpu cortex-a72是比较稳妥的选择。如果 QEMU 版本较新可以试试-cpu neoverse-n1性能会好一些。4.2 磁盘空间管理qcow2 磁盘文件只增不减用久了会占用大量空间。定期做一次压缩qemu-img convert -O qcow2 -c ubuntu-arm64.qcow2 ubuntu-arm64-compressed.qcow2-c参数表示压缩。转换完成后用新文件替换旧文件。注意转换过程中需要额外的磁盘空间存放新文件确保宿主机有足够空间。另一个技巧是在虚拟机内部定期执行fstrimsudo fstrim -av这会让虚拟机通知 QEMU 哪些块已经不再使用QEMU 可以据此回收空间。不过 Windows 上的 QEMU 对 TRIM 的支持不太完善效果有限。4.3 快照与备份qcow2 格式支持内部快照可以在关键时刻保存虚拟机状态qemu-img snapshot -c clean_install ubuntu-arm64.qcow2这行命令创建了一个名为clean_install的快照。之后如果系统被折腾坏了可以恢复到快照状态qemu-img snapshot -a clean_install ubuntu-arm64.qcow2实操心得在安装完系统、配置好基础环境之后立刻创建一个快照。这样后面无论怎么折腾都能一键回到干净状态。比重新安装系统省事得多。4.4 文件传输的几种方式在宿主机和 ARM 虚拟机之间传文件有几种常用方法SCP最直接的方式通过 SSH 通道传输。scp -P 2222 local_file.txt ubuntulocalhost:~/Samba 共享在虚拟机里装 Samba把目录共享出来Windows 上通过网络邻居访问。配置稍复杂但用起来方便。QEMU 共享文件夹QEMU 支持 virtio-9p 或 virtiofs 共享宿主机目录但 Windows 版本的支持不太稳定不推荐。Git 仓库如果代码已经在 Git 仓库里直接在虚拟机里 clone 就行不需要从宿主机传。我平时用得最多的是 SCP简单可靠。大文件传输时速度取决于网络配置用户模式网络下大概能跑到几十 MB/s够用了。5. 常见问题与排查实录5.1 启动失败类问题问题一QEMU 启动后黑屏没有任何输出。最常见的原因是 UEFI 固件文件路径不对。检查-bios参数指向的文件是否存在。如果路径中有空格需要用引号括起来。另一个可能是显示设备配置有问题试试把-display default换成-display gtk或-display sdl。问题二提示 Could not open QEMU_EFI.fd: No such file or directory。QEMU 安装目录下的share文件夹里应该有这个文件。如果找不到可以从 QEMU 的安装包里单独提取或者从网上下载对应版本的 UEFI 固件。注意固件文件要和 QEMU 版本匹配太老的固件可能不支持新的 QEMU。问题三虚拟机启动后卡在 UEFI 界面找不到启动设备。检查 ISO 镜像是否正确挂载。-drive参数中的file路径要写对formatraw要匹配 ISO 文件的实际格式。另外确认 ISO 文件是 arm64 版本不是 amd64 版本。5.2 安装过程类问题问题四安装过程中键盘鼠标没反应。检查 USB 设备配置。-device qemu-xhci提供 USB 控制器-device usb-kbd和-device usb-tablet分别提供键盘和鼠标。这三个缺一不可。如果还是不行试试把usb-tablet换成usb-mouse。问题五安装速度极慢几个小时都没装完。ARM 模拟本身就慢但异常慢通常是资源分配不足。检查内存是否给了至少 2GBCPU 核心是否给了至少 2 个。另外确认宿主机没有其他重负载任务在跑。如果宿主机是机械硬盘换成 SSD 会有明显改善。问题六安装完成后重启又进入了安装界面。这是因为 ISO 镜像还在启动项里UEFI 优先从光驱启动了。去掉挂载 ISO 的-drive参数或者调整启动顺序。5.3 网络类问题问题七虚拟机里无法访问外网。用户模式网络下虚拟机应该能自动通过 NAT 访问外网。如果不行检查虚拟机内部的网络配置ip addr show ip route show cat /etc/resolv.conf确认网卡拿到了 IP 地址通常是 10.0.2.15默认路由指向 10.0.2.2DNS 配置正确。如果网卡没起来试试sudo dhclient手动获取 IP。问题八宿主机无法 SSH 到虚拟机。检查hostfwd参数是否正确配置。hostfwdtcp::2222-:22表示把宿主机的 2222 端口转发到虚拟机的 22 端口。确认虚拟机里 SSH 服务正在运行sudo systemctl status ssh如果服务没起来sudo systemctl start ssh启动它sudo systemctl enable ssh设置开机自启。问题九TAP 网络配置后虚拟机无法上网。TAP 桥接网络在 Windows 上配置比较麻烦常见问题是网卡没有正确桥接或者 IP 地址冲突。检查 Windows 的网络适配器设置确认 TAP 网卡和物理网卡已经桥接。另外确认虚拟机的 IP 地址和宿主机在同一网段且没有和其他设备冲突。5.4 性能类问题问题十编译时频繁出现 Killed 或 Out of memory。这是内存不足导致的。ARM 模拟下编译大型项目4GB 内存可能不够。要么增加-m参数的值要么减少编译并行数。另外可以给虚拟机加一个 swap 分区作为缓冲sudo fallocate -l 4G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile问题十一磁盘 I/O 成为瓶颈。检查 qcow2 文件是否放在 SSD 上。如果宿主机有多块硬盘把 qcow2 文件放在最快的盘上。另外确认使用了ifvirtio而不是ifide。问题十二QEMU 进程占用 CPU 过高。ARM 模拟本身就是 CPU 密集型任务QEMU 进程占用高是正常的。但如果宿主机变得卡顿说明分配的核心数太多了。减少-smp的值给宿主机留出足够的资源。5.5 常见问题速查表现象可能原因解决方法启动黑屏UEFI 固件路径错误检查 -bios 参数路径找不到启动设备ISO 未正确挂载检查 -drive 参数键鼠无响应USB 设备未配置添加 qemu-xhci/usb-kbd/usb-tablet无法上网网络配置错误检查 ip addr 和路由SSH 连不上端口转发未配置添加 hostfwd 参数编译被 Killed内存不足增加 -m 或减少并行数磁盘空间暴涨qcow2 未压缩用 qemu-img convert -c 压缩安装极慢资源分配不足增加内存和 CPU 核心避坑技巧每次修改 QEMU 启动参数后先用-nographic模式跑一下看看有没有报错信息。确认没问题后再加显示输出。这样排查问题会快很多。6. 进阶玩法与扩展思路6.1 用脚本管理虚拟机每次手敲那一长串 QEMU 参数太麻烦写一个批处理脚本或者 PowerShell 脚本把参数封装起来。比如创建一个start-arm-vm.batecho off C:\Program Files\qemu\qemu-system-aarch64.exe ^ -m 4096 ^ -cpu cortex-a72 ^ -smp 4 ^ -M virt ^ -bios C:\Program Files\qemu\share\QEMU_EFI.fd ^ -device virtio-gpu-pci ^ -display default ^ -device qemu-xhci ^ -device usb-kbd ^ -device usb-tablet ^ -drive ifvirtio,formatqcow2,fileubuntu-arm64.qcow2 ^ -netdev user,idnet0,hostfwdtcp::2222-:22 ^ -device virtio-net-pci,netdevnet0以后双击这个脚本就能启动虚拟机不用记那些参数。6.2 多架构开发环境如果你同时需要 x86 和 ARM 两种环境可以装两个虚拟机一个跑 x86 Ubuntu一个跑 ARM Ubuntu。代码在两边分别编译测试确保跨平台兼容性。QEMU 也支持同时运行多个实例只要内存和 CPU 够用。6.3 配合容器使用在 ARM 虚拟机里装 Docker就可以跑 ARM 架构的容器镜像。这对于验证容器化应用的跨平台兼容性非常有用。装 Docker 的步骤和在普通 Ubuntu 上一样sudo apt install -y docker.io sudo systemctl enable --now docker sudo usermod -aG docker $USER重新登录后docker run --rm arm64v8/ubuntu uname -m应该输出aarch64。6.4 调试 ARM 二进制在 ARM 虚拟机里用 GDB 调试程序和 x86 上没什么区别。但如果想在宿主机上远程调试虚拟机里的程序可以用 gdbserver# 在虚拟机里 gdbserver :1234 ./my_program # 在宿主机上需要 ARM 版 GDB gdb-multiarch ./my_program (gdb) target remote localhost:1234这需要宿主机上安装支持 ARM 架构的 GDB通常用gdb-multiarch这个包。6.5 快照链与增量备份qcow2 支持快照链可以基于一个基础镜像创建多个增量快照。这在测试不同配置时很有用创建一个干净系统的基础快照然后每个测试场景基于它创建一个增量快照互不干扰。# 创建基础镜像 qemu-img create -f qcow2 -b base.qcow2 -F qcow2 test1.qcow2 # 启动测试环境 qemu-system-aarch64 ... -drive filetest1.qcow2 ...这样test1.qcow2只存储和base.qcow2的差异部分节省空间。测试完成后直接删除test1.qcow2即可不影响基础镜像。6.6 性能监控在宿主机上监控 QEMU 进程的资源占用Get-Process qemu-system-aarch64 | Select-Object CPU, WorkingSet, VirtualMemorySize在虚拟机内部用htop或top查看系统负载。如果发现 CPU 使用率长期 100%说明模拟性能已经到瓶颈了考虑优化代码或者换更快的宿主机。我个人在实际操作中的体会是ARM 全系统模拟最适合的场景是功能验证和兼容性测试不适合做性能基准测试。模拟环境下的性能数据和真实硬件差异很大只能用来判断“能不能跑”不能用来判断“跑得快不快”。如果你需要准确的性能数据还是得用真实的 ARM 设备。但作为日常开发调试环境QEMU 模拟的 ARM Ubuntu 已经足够好用省去了来回切换设备的麻烦代码写完直接在本地验证效率提升非常明显。