ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Ubuntu下用QEMU模拟ARM64环境:从安装到踩坑全指南

Ubuntu下用QEMU模拟ARM64环境:从安装到踩坑全指南 1. 为什么要装QEMUx86宿主机上的异构架构需求很多人在Ubuntu里装虚拟机第一反应是VirtualBox或者VMware。这两个工具确实好用但它们解决的都是“x86跑x86”这类同构虚拟化。你可以很轻松地用它们装一个另一个版本的Ubuntu但如果你想在x86电脑上跑一个ARM64系统、做嵌入式交叉编译或者验证RISC-V程序这两个软件就无能为力了。这个场景恰恰是QEMU的主场。QEMU的全称是Quick Emulator它不要求宿主机和虚拟机同架构。它靠TCG动态二进制翻译技术把ARM64的指令实时翻译成宿主机能执行的指令。翻译执行肯定有性能损失但换来的是架构无关的能力——你可以在任何架构上模拟几乎任何架构。我最初接触QEMU就是因为开发板不在手边客户催着要复现一个ARM64环境里的内核崩溃问题最后全靠QEMU在x86机器上把这个环境搭了出来。这一用就再也没离开过。在实际工作中QEMU最常见的应用场景有这么几类嵌入式开发板子不在身边时用QEMU模拟ARM64环境复现问题、验证启动脚本、测试内核补丁。多架构CI编译和测试流水线要覆盖多套CPU架构不可能给每个架构都配真机QEMU是成本最低的替代方案。学习与体验想试玩新的发行版、体验不同CPU特性又不想折腾实体机和双系统。这篇文章后面的内容我会围绕一个最典型的场景来写在x86架构的Ubuntu宿主机上用QEMU模拟ARM64aarch64环境。这个场景的坑最多也最贴合实际开发需求。安装方法、参数理解、踩坑思路对其他架构都是通用的看完你可以触类旁通。2. 先认清QEMU的软件包结构少走一半弯路如果你直接执行sudo apt install qemuUbuntu会装上一堆包然后你对着命令行里的qemu-system-x86_64、qemu-system-arm、qemu-aarch64、qemu-img这些命令发呆不知道该用哪个。这个迷茫很正常因为QEMU不是一个单一的程序而是一整套模拟器框架。QEMU大致分成三个层面。第一层是系统模拟器用来跑完整操作系统命令格式是qemu-system-架构比如qemu-system-aarch64、qemu-system-arm、qemu-system-riscv64。第二层是用户态模拟器用来跑单个二进制程序命令格式是qemu-架构或者qemu-架构-static比如qemu-aarch64、qemu-aarch64-static。第三层是工具集包括qemu-img、qemu-io、qemu-nbd这些负责磁盘镜像、块设备读写之类的事务性工作。Ubuntu软件包的拆分方式和这三层严格对应。在Ubuntu 22.04之类的系统上你会看到以下这些常见包qemu-system-armARM相关系统模拟器不同Ubuntu版本里可能包含aarch64的二进制。qemu-system-x86x86系模拟器。qemu-system-misc其他各种较小众的架构。qemu-user / qemu-user-static用户态模拟器static版本配合binfmt机制可以透明执行跨架构二进制。qemu-utilsqemu-img等磁盘工具。qemu-efi-aarch64ARM64虚拟机的UEFI固件没有它ARM64虚拟机很难启动。qemu-block-extra额外的块设备驱动后端。这种按架构拆分的包装策略有它的道理。QEMU支持几十种架构如果打包成一个光二进制就好几个GB。大多数人实际只用到一两个架构按架构安装可以省很多磁盘空间。所以安装前先想清楚你只需要模拟ARM64那装qemu-system-arm、qemu-utils、qemu-efi-aarch64这几个就够了不用把家都搬过来。有一个特别容易忽略的包叫qemu-user-static。如果你要在x86的Ubuntu上直接运行ARM64的二进制或者chroot到一个ARM64的rootfs里这个包是刚需。它配合binfmt-support可以让Linux内核自动识别ARM64的ELF文件并交给模拟器执行。我后面会专门讲到这个用法。3. Ubuntu下两条安装路线apt二进制包与源码编译3.1 apt直接安装覆盖绝大多数场景先介绍最快能跑起来的方式。在终端里依次执行sudo apt update sudo apt install qemu-system-arm qemu-utils qemu-efi-aarch64这里特意装了三个包各司其职qemu-system-arm负责系统模拟的核心qemu-efi-aarch64提供ARM64虚拟机的UEFI固件qemu-utils提供qemu-img等磁盘工具。如果后面要配置用户态模拟再补一个qemu-user-static就行。一次把这几个装好能节约很多来回折腾的时间。装完验证一下版本qemu-system-aarch64 --version能看到版本号说明安装成功。在Ubuntu 22.04上这个版本通常是6.2.0在Ubuntu 24.04上会到8.x。版本重要吗重要但不用过度纠结能用就行。等哪天遇到某个新参数不被支持再考虑升级来源。我在实际使用中建议只要磁盘空间充足干脆把qemu-system这个元包装上。它会把x86、arm、mips、riscv等常见架构的模拟器一次性带进来以后想换个架构测试不用再补装。别小看这一步我经常在调试一个和异构平台相关的CI问题时临时要模拟一个冷门架构结果缺包又得sudo apt install很打断节奏。3.2 源码编译什么时候值得折腾有些场景apt安装满足不了。我遇到过两个典型情况。一是系统源的QEMU版本太旧缺某个CPU型号或者machine type。比如某些新型ARM开发板对应的机器类型老版本QEMU根本不认识。二是需要打补丁调试QEMU自身或者需要特殊的configure选项。源码编译的流程并不复杂核心步骤如下sudo apt install build-essential ninja-build python3-venv pkg-config libglib2.0-dev libpixman-1-dev flex bison git clone https://gitlab.com/qemu-project/qemu.git cd qemu ./configure --target-listaarch64-softmmu,aarch64-linux-user --enable-slirp make -j$(nproc)敲命令之前先理解几个关键点。--target-list里softmmu代表系统模拟器linux-user代表用户态模拟器。如果只需要跑ARM64系统填aarch64-softmmu就够了。只编一个target的话依赖环境比较好的机器上十分钟左右就能编译完比全量编译少一个数量级的时间。配套的编译依赖也必不可少特别是libglib2.0-dev和libpixman-1-dev缺了它们configure阶段就会直接报错。编译完成后的二进制在build目录下比如build/qemu-system-aarch64直接调用这个路径即可。不想用绝对路径的话可以make install装到/usr/local/bin然后执行hash -r让shell刷新命令缓存。源码编译和apt安装可以共存互不影响/usr/local/bin在默认PATH里的优先级排在/usr/bin之前所以你make install之后命令行里优先调用的就是新版本。如果觉得源码编译的维护成本有点高还有个折中方案使用QEMU的稳定PPA源比如ppa:jacob/virtualisation。这个源会持续提供较新的QEMU版本不用自己编译。不过第三方源毕竟不是官方发行渠道生产环境里要不要用得自己权衡一下风险。4. 模拟ARM64的完整实操镜像准备、UEFI固件与首次启动4.1 先确认宿主机架构避免方向性错误开始之前先跑一句uname -m。如果输出是x86_64接下来会走TCG模拟路径如果输出是aarch64说明你本身就在ARM机器上这种时候反而可以考虑用KVM加速性能会好很多。这篇文章以x86_64宿主机为主ARM宿主机上的差异我会在下文顺带提。4.2 准备系统镜像ISO安装盘与cloud imageQEMU本身不提供操作系统需要自己准备一个ARM64的Ubuntu系统镜像。去Ubuntu官网下载的时候认准名字里带arm64的服务器版ISO千万别下成amd64的。如果下错架构后面启动阶段会出现一堆莫名其妙的引导错误。ISO安装适合交互式安装流程和在x86虚拟机上装系统差不多。但问题在于ARM64的server版ISO安装界面在无窗口模式下引导有时候不顺利。所以我更推荐用cloud image也就是云镜像。它的体积小、启动快适合快速搭建测试环境。下载cloud image之后它本身就是一个qcow2格式的磁盘镜像比如ubuntu-24.04-server-cloudimg-arm64.img。可以先复制一份再用qemu-img扩容qemu-img create -f qcow2 ubuntu-dev.img 20G qemu-img convert -f qcow2 -O qcow2 ubuntu-24.04-server-cloudimg-arm64.img ubuntu-dev.img这样才能把磁盘扩展到20G原镜像的空间通常比较紧张。这里顺便解释一下qcow2和raw的区别。qcow2是QEMU自家的镜像格式支持稀疏文件、快照、写时复制创建一个20G的文件并不会马上占满20G磁盘而是随着写入慢慢变大。raw格式更朴素性能通常好一点点但空间不会自动节省。对大多数使用场景来说qcow2是更合适的选择。4.3 UEFI固件ARM64虚拟机的起搏器在x86虚拟机里有个东西叫BIOSQEMU会自动加载SeaBIOS用户基本不用管。但ARM64的virt机器类型不内置固件你得手动给它一个UEFI固件文件否则开机就是黑屏、无反应。前面让装的qemu-efi-aarch64包就是干这个的。装好后固件文件位于/usr/share/qemu-efi-aarch64/QEMU_EFI.fd如果系统里找不到这个路径可以执行dpkg -L qemu-efi-aarch64查看实际位置。这个文件本质是ARM64版的UEFI实现类似x86上的OVMF固件负责在虚拟硬件上加载引导程序再引导操作系统。UEFI固件有一个常见坑如果用-pflash参数挂载固件QEMU会尝试把它当作可写变量区某些版本要求你同时提供两份拷贝或者组织成特定的flash布局。最简单的做法是用-bios参数直接加载固件文件它以只读方式工作对新手最友好。下面给的命令用的就是-bios。4.4 首次启动完整命令与参数拆解假设已经准备好一个cloud image并复制扩容到了ubuntu-dev.img启动命令可以写成这样qemu-system-aarch64 \ -M virt \ -cpu cortex-a72 \ -smp 4 \ -m 4096 \ -bios /usr/share/qemu-efi-aarch64/QEMU_EFI.fd \ -drive ifnone,fileubuntu-dev.img,formatqcow2,idhd0 \ -device virtio-blk-device,drivehd0 \ -netdev user,idnet0,hostfwdtcp::2222-:22 \ -device virtio-net-device,netdevnet0 \ -nographic这条命令里的每个参数都不是摆设。逐一拆开说-M virt选择虚拟机的machine type。virt是QEMU为虚拟化场景设计的通用ARM平台不绑定具体开发板的硬件布局灵活性最好。千万别一上来就选raspi3b这类具体板卡那不是给标准Ubuntu镜像用的。-cpu cortex-a72指定CPU型号。想省心可以直接用-cpu max表示把当前QEMU版本能提供的所有ARM CPU特性全部打开代价是不同版本之间的行为可能有差异。-smp 4分配4个vCPU-m 4096分配4GB内存。-bios加载UEFI固件刚才已经解释过。-drive和-device分别定义磁盘后端和前端控制器。ifnone表示不要自动推断接口file指定镜像路径format指定镜像格式。devic e virtio-blk-device把磁盘接到virtio总线前端。QEMU这样设计的目的是把存储后端和前端设备类型解耦方便自由组合。-netdev和-device同理前半段定义网络后端这里用的是user模式的NAT后半段把虚拟网卡接到这个后端。hostfwdtcp::2222-:22是关键配置它把宿主机的TCP 2222端口转发到虚拟机的22端口这样之后就能ssh连进去。-nographic以无图形界面方式运行将虚拟机的串口重定向到当前终端。服务器环境没有显示器也能正常工作。执行这条命令后你会看到UEFI启动日志随后进入GRUB菜单最后出现登录提示。cloud image的默认用户是ubuntu但初始密码和SSH密钥需要额外配置。如果不打算折腾这个问题有两个选择一是用ISO安装盘手动装一遍系统二是用cloud-localds生成一个seed镜像把初始用户数据注入进去。生成seed镜像的流程是这样的sudo apt install cloud-image-utils创建一个user-data文件内容如下#cloud-config password: yourpassword chpasswd: { expire: False } ssh_pwauth: true然后生成seed.imgcloud-localds seed.img user-data再启动时增加一块virtio驱动器-drive ifnone,fileseed.img,formatraw,idseed \ -device virtio-blk-device,driveseedcloud-init服务在虚拟机启动时会自动扫描这块虚拟设备读取里面的配置创建用户、设置密码、开启SSH密码登录。cloud image必须配cloud-init数据才能真正可用这一步几乎每个用cloud image的人都会踩一遍。5. 虚拟机联网与文件共享别让环境变成孤岛5.1 网络模式怎么选前面启动命令里用到的-netdev user就是QEMU的用户模式网络也叫SLIRP。它是内建在QEMU进程里的一套NAT实现不需要宿主机有root权限虚拟机可以上网、可以访问外部网络但外部网络无法直接访问虚拟机内部。这个模式适合大多数单机开发和调试场景。如果需求升级了比如需要让局域网里其他机器也能直接访问虚拟机或者虚拟机要对外提供某个服务user模式就撑不住了。这时候需要tap网络。tap模式要求root权限在宿主机上创建tun设备并把tap接口和宿主机网桥接起来。大致步骤是这样sudo ip tuntap add dev tap0 mode tap sudo ip link set tap0 up sudo ip link set tap0 master br0QEMU启动参数改成-netdev tap,idnet0,ifnametap0,scriptno,downscriptno \ -device virtio-net-device,netdevnet0tap模式配置比user模式复杂得多多一只网卡要管理配不好还容易把宿主机网络搞乱。我的建议很明确默认场景老老实实用user模式只有明确需要外部访问虚拟机时才切tap并且先在测试机上验证通过再上生产宿主机。5.2 端口转发最常用的远程登录方式user模式配合hostfwd能覆盖90%的远程开发需求。前面命令里hostfwdtcp::2222-:22意思是把宿主机TCP 2222端口映射到虚拟机的22端口。启动后就能这样登录ssh -p 2222 ubuntu127.0.0.1如果虚拟机里SSH服务没起来先进虚拟机装好sudo apt install openssh-server sudo systemctl enable --now ssh在cloud image里sshd可能默认没启用或者被cloud-init配置成只允许密钥登录。这个坑我踩过好几次每次卡在ssh连不上最后发现guest里sshd根本没启动或者PasswordAuthentication是关闭的。所以cloud-init配置里要显式写上ssh_pwauth: true。5.3 文件共享方案虚拟机跑起来之后怎么和宿主机交换文件最省事的方式是scpscp -P 2222 file.txt ubuntu127.0.0.1:/home/ubuntu/零配置先决条件只是能ssh登录。另一个常用方案是QEMU的virt-9p共享目录。在宿主机启动命令里加一段-virtfs local,path/home/user/share,mount_taghostshare,security_modelnone,idshare0然后在虚拟机里挂载sudo mount -t 9p -o transvirtio,version9p2000.L hostshare /mnt/share9p的优点是天然集成在QEMU里不需要额外启动服务适合共享源码目录、脚本之类的小文件集合。缺点是性能一般大文件传输和大量小文件读写时能明显感觉到慢。如果要在虚拟机里跑构建任务、传大型固件还是建议用scp或者单独搭一个NFS服务。可能有人会问为什么不用virtiofsvirtiofs性能确实更好但它要求宿主机侧有virtiofsd进程对QEMU版本和内核模块要求都更高Ubuntu发行版默认不一定成套配齐。相比之下9p在qemu-system-aarch64里开箱即用稳定性足够是我在这个场景下的首选方案。6. 踩坑记录连续启动失败背后的完整排查链路6.1 报错“KVM not available”不一定代表环境坏了我最早在Ubuntu上敲QEMU命令时下意识把在x86虚拟机里习惯用的-enable-kvm参数也带上了结果报错qemu-system-aarch64: -enable-kvm: invalid accelerator kvm第一反应是“完了虚拟化没开”。我当时马上查lscpu看vmx/svm标志ls /dev/kvm看设备文件一通操作猛如虎发现宿主机KVM其实完全正常。问题到底出在哪里是我混淆了KVM的作用范围。KVM利用处理器的硬件虚拟化扩展让同架构的虚拟机可以直接跑比如x86宿主机跑x86虚拟机。而x86宿主机上模拟ARM64连指令集都不一样硬件虚拟化帮不上忙只能靠TCG做二进制翻译。这个报错不是环境坏了是参数用错了。正确做法是去掉-enable-kvm或者明确写-accel tcg。TCG模式下ARM64模拟速度比真机慢不少但跑编译、启动系统、看日志完全够用。如果想尽可能多地开启ARM CPU特性把-cpu cortex-a72换成-cpu max即可。复盘一下这次排查报错信息其实已经把问题指向了accelerator但不要被这个术语吓住。先想清楚自己的架构关系比盲目重装驱动、重装虚拟化组件有用得多。6.2 启动黑屏、卡死无输出多半是固件或串口问题第二个坑印象更深刻。去掉KVM参数之后命令能跑了但终端一片黑CPU占用还一直很高像是被什么东西卡住了。第一次遇到这种状况很容易怀疑是系统镜像坏了。其实不是。我当时的排查顺序是先去掉-nographic打开图形窗口跑一次看有没有图像输出。如果图形窗口也是黑的再确认UEFI固件路径是否正确。路径写错会在日志里直接提示找不到文件可如果路径对了、固件加载了依然黑屏那就要检查串口和console的配置。-nographic参数只是禁用了QEMU的图形输出然后把串口接到标准输入输出。但如果UEFI或内核把日志输出到了VGA而不是串口你还是什么都看不见。解决办法是加上-serial mon:stdio同时确保内核引导参数里有consolettyAMA0。绝大多数Ubuntu ARM64镜像的默认内核已经支持ARM串口输出所以关键别把串口参数弄丢。我最后加上-serial mon:stdio之后日志终于哗哗地刷出来了。还有一个容易被忽略的细节如果用UEFI里的GRUB引导安装界面在-nographic模式下不一定能自动切换到串口显示。遇到这种情形不如先开图形窗口把系统装完再用-nographic模式正常运行。在无窗口模式下死磕安装引导纯属浪费时间。6.3 SSH连不上的三条排查线虚拟机起来了日志也刷完了到了输入账号密码的步骤结果发现SSH完全连不上。这应该是QEMU新手遇到最多的问题了。我的排查固定按三条线走先确认虚拟机内部网络是否正常。在虚拟机里执行ip a看网卡有没有拿到IP。如果网卡有IP但宿主机ping不通虚拟机user NAT模式下外部访问本来就受限这个现象属于正常。检查端口转发是否生效。如果启动命令里没有hostfwdtcp::2222-:22宿主机就压根没有端口映射到虚拟机的22端口。在宿主机上执行ss -tlnp | grep 2222看有没有监听端口。检查虚拟机里的sshd是否运行。cloud image默认禁止密码登录甚至不开sshd的情况我都遇过。要么在cloud-init配置里显式开启ssh_pwauth和设置密码要么进虚拟机手动启动sshd服务。沿着这三条线排查下来九成问题都能落地。我最后一次连不上问题居然出在端口号上一边在命令里写了2222另一边ssh记成了22222多敲一个2当然连不上。这种低级错误往往最耗时。6.4 apt源里QEMU版本过老换PPA还是源码编译最后一个比较容易忽视的坑某些老版本Ubuntu官方源里的QEMU版本偏旧个别新参数得不到支持。比如我想用-cpu max时老版本可能直接报“找不到CPU模型”。解决办法有两条路升级QEMU版本或者避开新特性。优先级我建议先避开毕竟大部分功能老版本都有替代方案。如果非要新版本优先用稳定PPA其次才是源码编译。源码编译步骤前面已经写过只编aarch64-softmmu一个target时间成本完全可控。这里分享一个判断参数是否被支持的小技巧。执行qemu-system-aarch64 -cpu help可以查看当前版本支持的全部CPU模型执行qemu-system-aarch64 -machine help可以查看所有machine type。任何参数动手之前都可以先用help确认一遍很多坑在敲命令之前就能避开。7. 从模拟系统到异构调试QEMU的进阶玩法7.1 用户态模拟在x86上直接跑ARM64二进制前面讲的全是跑完整ARM64操作系统。如果只是交叉编译了一个程序想在本机快速验证行为用qemu-user会更轻量。装好qemu-user-static和binfmt-support之后Linux内核通过binfmt_misc机制自动识别ARM64的ELF文件并交给qemu-aarch64-static执行。你不必在命令行手动指定解释器直接./aarch64-binary就能跑。chroot进ARM64的rootfs也是一样的思路进入chroot后仿佛换了一个ARM64环境底层还是QEMU在做指令翻译。这个方案特别适合交叉编译工作流。比如给ARM64设备交叉编译了一个命令行工具想验证它的参数解析、跑一遍单元测试完全不需要启动一整个虚拟机。qemu-user的启动开销比qemu-system小一个数量级跑CLI工具、跑测试集都很快。7.2 用QEMU配合GDB做内核调试QEMU的系统模拟器天然支持GDB远程调试。启动时加两个参数结合前面的完整命令一起用-S 启动后挂起CPU等待调试器连接 -s 在TCP 1234端口打开GDB服务器在宿主机上用gdb-multiarch连接gdb-multiarch vmlinux (gdb) target remote :1234我最常拿这个模式做Linux内核断点调试。在x86宿主机上模拟ARM64内核从入口处逐步跟踪查看寄存器状态和内存布局。真机上做这种操作风险极高但在QEMU环境里随便折腾重启一下虚拟机就回到初始状态。做内核开发的不少人会专门把QEMU配置成最小启动目标就是为了能快速验证一个patch的效果。7.3 从QEMU到开发板参数差异与平台迁移很多人用QEMU跑通之后都会问这跟真机开发板差别有多大我的答案是大方向一致细节有差别。QEMU的virt机是一个通用虚拟平台它的串口、中断控制器、时钟等外设布局和多数真实开发板都不一样。你验证的是内核逻辑和系统行为不是具体驱动时序。如果需要模拟某个具体开发板比如树莓派或者某些RISC-V开发板QEMU也提供了对应的machine type。但具体模拟到什么粒度、能不能覆盖板上全部外设完全取决于QEMU对该板卡的维护状态。用之前先查官方文档别想当然。Ubuntu ARM64这种主流系统老老实实用virt机器最稳。真需要高仿真度调试固件、引导程序的话可以研究QEMU官方文档里的Board Support矩阵以及每个machine type支持的具体设备列表。这个话题比较深等有明确板卡需求再深入也不迟。最后说点个人习惯。QEMU命令越写越长之后我会把启动参数整理成一个脚本放到~/bin/qemu-ubuntu-dev.sh把磁盘路径、端口号、内存大小都固定下来。换机器迁移环境时只要把镜像文件拷过去脚本几乎不用改。如果经常在多个架构之间切换就给每个架构建一个独立脚本命名成qemu-arm64、qemu-x86这种一目了然的形式。这些细节不复杂但能省下大量重复敲命令的时间。
返回列表