
1. 引言为什么要折腾ARM64虚拟机如果你手上没有ARM开发板但又迫切需要验证ARM64架构下的软件行为、交叉编译产物或者容器镜像兼容性那在x86_64的CentOS 8主机上用QEMU模拟ARM64虚拟机就是一条非常务实的路子。我最早折腾这个场景是因为一份需要在ARM64服务器上运行的静态编译二进制本地只有x86的机器CI跑在别人的ARM机器上排队又慢。后来发现直接用libvirt管理QEMU/KVM往ARM64方向一配既能把内核、根文件系统、UEFI固件全按真实ARM服务器的启动流程走一遍又能随时快照回滚比反复往开发板刷系统舒服太多。这篇内容适合谁适合已经有一台CentOS 8主机、对Linux虚拟化有基础概念、但没怎么碰过跨架构模拟的读者。你会搞明白arm64和amd64到底差在哪UEFI固件在ARM虚拟化里扮演什么角色以及那些“BIOS能启动、UEFI起不来”“网卡死活不通”“控制台没输出”之类的坑都是什么原因造成的。需要说明一点QEMU跨架构模拟也就是常说的“翻译执行”性能肯定不如同架构KVM直通但这套方案的价值在于环境一致性和可复现性。你拿它跑编译、跑测试、跑CI完全够用要是想当生产级ARM服务器用那还是买实体ARM机器更实在。2. 原理先行libvirt、QEMU/KVM与ARM64的配合方式2.1 模拟与虚拟化的区别为什么ARM64能在x86上跑先说一个很多人混淆的概念。“虚拟化”通常指KVM这类硬件辅助虚拟化技术它依赖CPU提供的VMX/SVM指令让虚拟机里的指令直接跑在物理CPU上。而“模拟”是QEMU的TCGTiny Code Generator模式它把ARM64指令翻译成x86指令再执行。KVM可以理解为“硬件加速通道”QEMU是“前端控制面”。当你用QEMU/KVM跑同架构虚拟机时走的是KVM加速当你用QEMU跑跨架构虚拟机时KVM帮不上忙QEMU会自动落到TCG模拟模式。因此arm64虚拟机在x86主机上默认就是纯软件模拟性能损耗主要在这些指令翻译上这是物理限制换哪个发行版都一样。知道这个原理之后你就能理解一个现象同架构虚拟机里能看到KVM加速标志跨架构虚拟机里没有。后续排错时如果发现QEMU进程没有-accel kvm参数不要慌这是正常现象。2.2 UEFI固件在ARM64虚拟化中的角色ARM64服务器和PC一样现在默认走UEFI启动。和x86的UEFI主要存在固件里的方式不同QEMU模拟ARM64虚拟机时需要给虚拟机提供一个UEFI固件文件这个文件通常是编译好的QEMU_EFI.fd或者发行版自带的AAVMF固件。我们常说的“AAVMF”就是ARM架构的OVMFUEFI固件实现。它负责初始化CPU、内存、外设然后从启动介质里读取引导程序最终把内核加载起来。如果你不给虚拟机配UEFI固件QEMU默认可能尝试直接加载内核启动通过-kernel参数那走的就是另一条完全不同的启动路径很多分布式的镜像在这种模式下根本无法启动。所以搭建ARM64虚拟机的关键第一步就是确认主机上有没有可用的AAVMF固件文件。CentOS 8里通常通过edk2-aarch64这个包安装安装之后固件路径一般在/usr/share/AAVMF/下。2.3 宿主机的虚拟化基础libvirt和QEMU的关系libvirt是一套虚拟化管理API它负责把虚拟机的XML定义翻译成具体的QEMU命令行并管理生命周期。你当然可以绕过libvirt直接敲QEMU命令行但那样做不方便做网络管理、快照、迁移配置也需要手动维护。既然教程目标是“保姆级”我们老老实实用libvirt。它在CentOS 8上的组件有libvirt-daemon、libvirt-daemon-driver-qemu、virt-install命令行工具等。安装之后libvirtd服务会常驻虚拟机XML文件默认放在/etc/libvirt/qemu/下和VM共用一个命名空间。网络方面libvirt默认提供一个defaultNAT网络本质上是一个virbr0网桥加dnsmasq虚拟机通过它访问外网。但ARM64虚拟机场景里网络经常出问题常见原因是固件里没有加载网卡的UEFI驱动或者网卡型号选择不合适后面会专门讲。3. 实操环境准备与基础软件安装3.1 检查宿主机CPU和内核特性先别急着装包确认一下宿主机基础条件。要求是x86_64架构CPU支持虚拟化虽然跨架构模拟用不到KVM但libvirt和QEMU的运行依赖宿主内核的能力。uname -m # 输出 x86_64 就对了 lscpu | grep -E Architecture|Virtualization|Model name grep -E vmx|svm /proc/cpuinfo | head -1如果输出里没有vmx或svm说明CPU虚拟化扩展没开但这不影响我们进行跨架构模拟。另外确认CentOS 8能用dnf安装软件包网络通不通无所谓能访问官方源或者本地源就行。3.2 安装libvirt、QEMU和virt-installCentOS 8的AppStream源里自带这些包直接一条命令装齐dnf install -y qemu-kvm libvirt virt-install edk2-aarch64装完之后启动libvirtd服务并设置开机自启systemctl enable --now libvirtd systemctl status libvirtd确认没有报错后用virsh version看一眼输出的QEMU版本最好是支持ARM64 virt机型、TF-ATrusted Firmware-A和AAVMF的版本。CentOS 8自带的QEMU 4.2版本实测支持ARM64虚拟机的模拟不需要额外编译新版本。3.3 确认AAVMF固件路径edk2-aarch64包安装后会提供UEFI固件常见路径ls -l /usr/share/AAVMF/ # 典型输出 # AAVMF_CODE.fd # AAVMF_VARS.fd其中AAVMF_CODE.fd是固件代码AAVMF_VARS.fd是NVRAM变量存储区。我们给虚拟机分配独立的VARS副本不要直接复用同一个文件否则虚拟机之间会互相污染UEFI启动项这是后话。如果固件路径不同可以用rpm -ql edk2-aarch64 | grep AAVMF查一下实际安装位置。4. ARM64虚拟机配置详解4.1 选择合适的固件和机型创建ARM64虚拟机时有两个核心参数--arch aarch64和--machine virt。ARM64的QEMU支持多种机型最通用的是virt。它不是一个具体开发板型号而是QEMU抽象出来的通用虚拟平台兼容性好UEFI、PCIe、virtio设备都支持。另一个常见选择是-M virt-4.2这种带版本号的不过libvirt自动管理机型兼容性我们一般不手动指定具体版本。固件配置上ARM64虚拟机需要指定UEFI固件。在libvirt的XML里这个问题经常被写错。4.2 使用virt-install创建虚拟机继续用virt-install创建虚拟机示例命令virt-install \ --name arm64-vm \ --memory 4096 \ --vcpus 4 \ --arch aarch64 \ --machine virt \ --os-variant centos8 \ --disk path/var/lib/libvirt/images/arm64-vm.qcow2,size20,formatqcow2,busvirtio \ --network networkdefault,modelvirtio \ --cdrom /var/lib/libvirt/boot/CentOS-8-Stream-ARM64.iso \ --boot uefi解释几个关键参数--arch aarch64告诉libvirt这是一台ARM64虚拟机QEMU会自动切换到模拟模式。--machine virt选择virt平台。--boot uefi让libvirt自动寻找AAVMF固件。如果libvirt版本较老可能会自动找错路径这时需要在XML里手动指定固件。执行过程中如果提示找不到固件先检查edk2-aarch64是否真的安装成功再手动编辑XML指定固件路径。4.3 手动XML配置要点如果你需要精细控制直接在virsh dumpxml arm64-vm的基础上改或者用virsh edit arm64-vm手动编辑。ARM64虚拟机XML里最核心的几段如下。UEFI相关配置通常放在os标签里os firmwareefi type archaarch64 machinevirthvm/type loader readonlyyes typepflash/usr/share/AAVMF/AAVMF_CODE.fd/loader nvram template/usr/share/AAVMF/AAVMF_VARS.fd/var/lib/libvirt/qemu/nvram/arm64-vm_VARS.fd/nvram boot devhd/ /os注意loader要标记readonlyyes并且使用pflash模式。UEFI固件代码在运行时是不允许写回的NVRAM变量则写入独立副本。一个常见的错误是忘加boot devhd/导致虚拟机启动时UEFI找不到启动设备只能进入UEFI Shell或者固件设置界面。磁盘设备推荐使用virtio总线disk typefile devicedisk driver nameqemu typeqcow2 cachenone/ source file/var/lib/libvirt/images/arm64-vm.qcow2/ target devvda busvirtio/ /disk网卡同样推荐virtiointerface typenetwork mac address52:54:00:xx:xx:xx/ source networkdefault/ model typevirtio/ /interface如果你在UEFI模式下发现网卡无法PXE启动或者系统内无法识别网口多半是model type选成了e1000或rtl8139。虽然这些老网卡在x86虚拟机里很常用但在ARM64的QEMU固件里virtio-net-pci的UEFI驱动要可靠得多。这个问题下面还要细说。5. 常见坑点与排查实录5.1 启动后黑屏/无控制台输出这是跨架构模拟最头大的问题之一。你可以先检查虚拟机的串口配置ARM64虚拟机在libvirt里的默认显示设备可能是VGA或virtio-gpu但很多ARM64系统镜像没有内置图形终端驱动启动信息会输出到串口。解决办法是给虚拟机添加串口控制台同时把内核和UEFI启动过程引到串口上serial typepty target typesystem-serial port0/ /serial console typepty target typeserial port0/ /console如果系统支持还可以在os标签的kernel args里加consolettyAMA0,115200这样内核启动日志也会打到串口上。实际上很多ARM64的云镜像默认就带串口控制台。查看串口输出的命令是virsh console arm64-vm如果卡在固件阶段之前先确认UEFI固件路径是否生效可以用virsh dumpxml检查loader是否真的被加载。5.2 UEFI启动时卡在UEFI Shell尽管设置了boot devhd/实际运行时还是可能进入UEFI Shell。原因通常是NVRAM变量文件里没有预设启动项或者磁盘上没有可引导的EFI分区。解决办法是先进入UEFI Shell输入exit回到固件菜单手动选择Boot Manager看看磁盘设备是否出现。如果出现但无法引导说明系统镜像的EFI引导记录有问题。还有一种情况Disk是qcow2格式但libvirt将磁盘挂载为USB/IDE设备UEFI固件里没有对应驱动导致看不到启动盘。解决方案是把磁盘总线改成virtio再用--boot uefi重新创建或编辑已有虚拟机。5.3 网络不通网络不通的原因很多第一个要排查的是网卡型号。ARM64虚拟机中尽量使用virtio-net不要用e1000。修改方式virsh edit arm64-vm找到interface标签把model typee1000/改成model typevirtio/保存重启虚拟机。如果网卡是virtio但仍然不通检查libvirt的default网络是否正常virsh net-list --all virsh net-start default然后看虚拟机内网卡有没有拿到IPip addr show如果虚拟机内没有NetworkManager或者网络服务没启动手动配置静态IP即可。这种问题与架构无关更多是系统镜像自带的网络管理工具差异导致的。5.4 在ARM64虚拟机里安装Docker/麒麟系统需要注意什么如果你跟我一样需要在ARM64虚拟机里跑Docker或者国产化系统比如麒麟要额外注意两个点。第一软件包架构。ARM64虚拟机里必须选择arm64架构的软件源和安装包。CentOS Stream 8的ARM64源在官方仓库里是单独的需要确保baseos和appstream仓库指向aarch64架构的镜像。第二Docker在ARM64虚拟机里能否跑核心取决于内核模块和系统调用兼容性。QEMU的TCG模拟会拦截所有系统调用再翻译运行Docker容器时额外的IO开销会放大性能损耗。实测编译小项目能接受跑大型数据库或者高并发服务就要做好性能急剧下降的心理准备。简单说ARM64虚拟机适合做验证不适合做性能测试。如果你只是验证“arm64的nginx镜像能不能跑起来”完全没问题。5.5 UEFI与TPMWin11 ARM64等系统的特殊情况标题相关的热词里出现了Win11 UEFI修复、TPM等这里顺带提一下。QEMU的ARM64虚拟机默认没有提供TPM设备如果你有特殊需求让ARM64虚拟机上跑Windows 11 ARM版而Windows 11要求TPM 2.0就需要手动添加虚拟TPM设备。在libvirt里添加swtpm设备需要额外安装swtpm包然后在XML的devices段加入类似配置tpm modeltpm-tis backend typeemulator version1.2/ /tpm不过Windows 11 ARM64在QEMU下的兼容性问题比正常Linux发行版多得多包括显卡驱动、串口调试、激活机制等建议不折腾。6. 性能优化与调试技巧6.1 CPU型号和拓扑的选择ARM64虚拟机默认的CPU型号可能是cortex-a57或者max。在QEMU 4.2里max会暴露尽可能多的CPU特性包括加密扩展、SVE等。可以通过virsh edit修改CPU配置cpu modecustom matchexact checknone model fallbackallowcortex-a72/model feature policyrequire namesve/ /cpuSVE可扩展向量扩展在ARMv8.2架构里存在某些新软件会用到。如果虚拟机内软件报“未支持的指令”一类错误可以尝试开SVE但要先确认QEMU版本支持。6.2 磁盘IO和缓存策略跨架构模拟下磁盘IO是性能瓶颈之一。建议使用virtio-blk或virtio-scsi镜像格式用qcow2缓存策略设置为none或writeback。如果追求极致的IO性能也可以将磁盘改成raw格式但会牺牲快照、压缩等qcow2功能。实测werbosity和快照功能不建议在追求性能时直接用raw除非你确定不需要快照。6.3 使用VNC/SPICE查看图形界面ARM64虚拟机如果不安装桌面环境其实不需要图形界面。但如果你用桌面镜像需要配置VNC或SPICE图形输出。graphics typevnc port5901 autoportno listen0.0.0.0 listen typeaddress address0.0.0.0/ /graphics然后用VNC客户端连到宿主机的5901端口。注意ARM64虚拟机的图形性能完全依赖QEMU模拟的GPU跑桌面会很卡建议只做安装系统用。7. 快速安装系统与自动化部署7.1 无人值守安装与cloud-init如果你需要批量创建ARM64虚拟机可以结合cloud-init。先在宿主机制作一个cloud-init seed镜像然后在virt-install时挂载cloud-localds seed.iso user-data meta-datavirt-install添加--disk pathseed.iso,devicecdrom系统启动后会自动运行cloud-init进行网络配置、用户创建等。这个方式特别适合那些官方提供qcow2云镜像的发行版比如Ubuntu、Debian、CentOS Stream、openEuler、麒麟等。下载对应aarch64的qcow2镜像然后直接以该镜像为磁盘启动虚拟机virt-install \ --name arm64-cloud \ --memory 4096 \ --vcpus 4 \ --arch aarch64 \ --machine virt \ --import \ --disk path/var/lib/libvirt/images/arm64-cloud.qcow2,formatqcow2,busvirtio \ --network networkdefault,modelvirtio \ --os-variant centos-stream8 \ --boot uefi和用ISO安装的区别是少了--cdrom参数多了--importlibvirt不再尝试启动安装程序而是直接从磁盘引导。7.2 给虚拟机做快照和回滚快照是虚拟机排错的神器。在跨架构模拟场景下系统装好了先做一个快照后续折腾环境搞坏了可以一键回滚。virsh snapshot-create-as arm64-vm --name clean-install --description 刚装完系统 virsh snapshot-list arm64-vm virsh snapshot-revert arm64-vm --snapshotname clean-install注意UEFI固件的NVRAM变量本身变化不一定包含在qcow2快照里如果启动项被改坏建议连NVRAM一起备份或者定期重置NVRAM文件。8. 写在最后的个人体会搭建ARM64虚拟机这事前几次折腾基本都是“卡在启动、卡在网络、卡在固件”这三个环节但只要把UEFI固件路径、virtio设备和串口控制台这三件事配顺剩下的问题就和普通虚拟机没什么两样了。我在实际使用中的一个小技巧是先在真机上用一个最小化的ARM64系统镜像验证完整启动链路再投入上去做具体业务验证。这样可以把“固件问题”和“业务问题”分离排错效率高很多。还有一个体会是QEMU的TCG模拟虽然比同架构KVM慢但它能帮你发现很多“x86上根本不会出现、ARM上才可能踩到”的问题比如字节序假设、依赖了x86指令集特性的汇编代码、以及约定架构相关的编译选项。从这个角度说这套环境对你的代码可移植性测试非常有价值。希望这篇过程记录能帮你少走几趟弯路。如果遇到新坑欢迎在评论区留言补充共同把这套方案的经验池填得更深。