用QEMU搭建鲲鹏Arm64 openEuler虚拟机学习环境
前一阵我准备把Arm64服务器软件生态好好过一遍想直接用鲲鹏硬件但暂时不具备条件最后把目标锁定在“鲲鹏Arm64 openEuler 虚拟机学习”这条路上。折腾完一轮之后我最大的感受是在虚拟机上熟悉这套组合成本低、可重来、能反复验证踩坑经验非常适合作为一个起点。这篇文章就完整记录我怎么搭出这台虚拟机、为什么这样搭、中间踩了哪些坑以及搭完之后怎么把它变成一套真正能练手的Arm64学习环境。适合第一次接触Arm64架构、想在本地模拟环境里熟悉openEuler系统、又不想一上来就买云服务器的人参考。1. 为什么我选择虚拟机作为学习的第一站Arm64服务器这几年在数据中心渗透得很快很多基础软件、中间件、数据库都已经针对AArch64做了适配。openEuler作为面向服务器场景的开源操作系统和AArch64指令集的配合也比较紧密所以“openEuler 鲲鹏Arm64”自然成了我研究服务器软件跨架构发布、编译和部署的一个样板环境。但问题很实际我没有现成的Arm64服务器主机直接买真机成本高放家里还有噪音、功耗、托管一堆事。云上的Arm实例虽然按需开通很方便但按小时付费意味着所有练习都要在计费状态下进行不利于慢慢折腾。对比下来虚拟机是个更合适的折中方案。选择虚拟机学习有四个很实在的好处环境可以随时销毁重建。装坏了、分区搞错了、依赖删错了不需要请求任何人帮忙删掉镜像重建一个新系统就行。这点对新手特别友好。可以在小尺寸下观察完整系统。启动openEuler的文本安装流程、配置软件源、安装开发工具链这些动作和真机没什么差别。能够提前发现软件迁移问题。很多库、编译选项、汇编片段的问题只有在真实AArch64上才会暴露虚拟机先把这一环补上。命令操作习惯可以提前养成。比如uname -m显示aarch64、用dnf装包、检查/proc/cpuinfo、确认二进制格式这些操作在虚拟机上练熟之后再到真机上迁移成本很低。我个人的另一个考虑是虚拟机再慢也是一个完整的Linux发行版容器。我可以用它跑通用的系统管理、编译、容器、网络实验这些技能和具体CPU型号无关练的是通用能力。真正需要压榨性能时才去考虑云上Arm实例。所以虚拟机不是“将就”而是一个定位清晰的练习设施。2. 环境准备与方案选型本地仿真还是云端实例2.1 先想清楚你要的是“能用”还是“接近真实”搭建Arm64学习环境第一步不是下载镜像而是想明白自己需要什么层级的“真实感”。如果你的目标是熟悉操作系统的安装流程、RPM包管理、命令行工具、软件编译逻辑那么本地用QEMU做指令集仿真就足够了。QEMU在x86主机上通过动态二进制翻译模拟AArch64指令运行效率远不如真机但系统调用语义、进程模型、内核接口都是正规的程序编译和执行的行为也是正确的。但如果你需要测性能、验证某些硬件特性指令、跑对CPU亲和力敏感的高并发服务本地仿真就不太够了。这时候应该考虑云上Arm虚拟机或自有真机。我后来把两条路都试了先本地虚拟机再云上实例前者的目标是“跑通”后者的目标是“跑准”。2.2 本地QEMU仿真与云端Arm实例对比下面这张表是我自己评估时的主要考虑对比项本地QEMU仿真云端Arm虚拟化实例硬件架构x86主机上动态翻译模拟AArch64物理机就是Arm处理器虚拟化层基本透明CPU加速通常没有KVM可用靠TCG软件模拟有硬件虚拟化支持性能接近裸机部署成本免费只需安装QEMU和固件按小时或包月计费有预算门槛网络访问NAT端口转发为主折腾桥接稍复杂分配公网/内网IP网络模型更接近生产适合场景系统安装、软件编译、基础运维练习性能测试、高并发压测、真实业务验证重建速度快一条命令启动或销毁快但涉及资源管理操作稍重踩坑价值高能碰到不少真实边界问题高不过付费状态下不敢拖太久我最终的方案是本地QEMU作为平时的主力学习环境负责“把流程跑通”等需要测试性能或者折腾SVE等特殊指令时再临时开启云上Arm实例。两条路线各有分工不是替代关系。2.3 本次的最终选型这次文章里的所有步骤以本地QEMU虚拟机为主线。主机是一台装了Debian系Linux的x86_64机器8核16G内存跑一个4核4G的Arm64虚拟机。如果你只有Windows方法也类似只要把QEMU换成Windows版本命令参数完全一致。虚拟机没有图形性能要求文本安装就能完成所以硬件门槛并不高。我强调一下这种本地仿真模式下CPU能力来自宿主机的软件翻译如果宿主机只有4核就别给虚拟机分配太多核否则所有vCPU来回抢占整体体验反而更差。内存倒是可以给足一般4G起步装系统时如果内存太少文本安装器会明显变慢甚至OOM。3. 完整搭建流程从下载镜像到SSH接入3.1 准备必要组件在Debian/Ubuntu系宿主机上执行下面的命令安装QEMU相关组件sudo apt update sudo apt install qemu-system-arm qemu-efi-aarch64 qemu-utils这里有三类东西缺一不可qemu-system-arm提供qemu-system-aarch64这个可执行文件负责模拟AArch64机器。qemu-efi-aarch64提供AArch64平台的UEFI固件系统安装必须靠它引导。少了这个虚拟机启动后大概率黑屏。qemu-utils提供qemu-img等管理工具用来创建磁盘镜像。如果你用的是Fedora或openEuler这类RPM系宿主机对应包名一般是qemu-system-aarch64和edk2-aarch64。核心思路都一样找到aarch64的QEMU模拟器找到对应的UEFI固件然后创建一块虚拟磁盘。3.2 下载openEuler的aarch64系统镜像接下来到openEuler官网下载对应aarch64架构的安装镜像。我选的版本是22.03系列的LTS版文件名里会明确包含aarch64别下成x86_64版本。下载完成后放在宿主机的一个固定工作目录里例如mkdir -p ~/arm64-lab cd ~/arm64-lab # 把下载的 openEuler-xx.xx-aarch64-dvd.iso 放到这个目录安装镜像一般有DVD和最小化两种形态。学习用的话DVD版更省心里面带了anaconda安装程序和其他基础安装源离线安装也不必担心缺包。3.3 创建虚拟磁盘磁盘镜像我用qcow2格式因为它按需增长不在一开始就占满40G空间qemu-img create -f qcow2 openEuler-arm64.qcow2 40G创建完成后会生成一个很小的文件真正用到哪块空间才逐步扩张。如果追求更高磁盘性能可以用raw格式但raw会一次性占用全部空间对学习环境来说没必要。3.4 编写并执行启动安装命令安装阶段最难的一步是参数选择。这里给一份我测试后比较稳的命令qemu-system-aarch64 \ -machine virt,gic-version3 \ -cpu cortex-a72 \ -smp 4 \ -m 4096 \ -bios /usr/share/qemu-efi-aarch64/QEMU_EFI.fd \ -cdrom openEuler-xx.xx-aarch64-dvd.iso \ -drive fileopenEuler-arm64.qcow2,formatqcow2,ifvirtio \ -netdev user,idnet0,hostfwdtcp::2222-:22 \ -device virtio-net-pci,netdevnet0 \ -nographic逐个解释一下关键参数为什么不是随便选的-machine virt,gic-version3virt是QEMU为AArch64提供的一种通用虚拟开发板兼容性好。gic中断控制器的版本选了3这比较接近现代Arm服务器的配置。-cpu cortex-a72指定虚拟机模拟的CPU型号。如果你不想具体到某个型号直接用-cpu max也行它能让模拟器暴露更多CPU特性。但为了贴近常见的Arm服务器处理器核心我用cortex-a72。-smp 4 -m 4096分配4个虚拟CPU和4G内存。在TCG仿真模式下CPU核数不是越多越好4个已经能保证安装和编译体验。-bios /usr/share/qemu-efi-aarch64/QEMU_EFI.fd指定AArch64系统的UEFI固件路径。这一步最容易被忽略也是最容易导致黑屏的原因。不同发行版放置固件的路径不同启动前可以用dpkg -L qemu-efi-aarch64找一下。-drive fileopenEuler-arm64.qcow2,formatqcow2,ifvirtio把虚拟磁盘挂载为virtio设备。virtio是虚拟化环境下的高性能半虚拟化接口在QEMU的AArch64模拟上表现比模拟IDE盘好很多。-netdev user,idnet0,hostfwdtcp::2222-:22使用用户态网络栈并把宿主机的2222端口转发到虚拟机的22端口这样之后就能用SSH连进去。-nographic无图形模式直接在终端里看安装界面。如果你是图形桌面环境想看到窗口可以去掉这个参数改用图形窗口显示但那样需要额外的显示支持和更多内存。我习惯用文本安装省资源。执行命令后大约等待几十秒会进入GRUB引导菜单。openEuler默认可能进入图形安装流程在GRUB界面按e进入编辑模式找到启动行并追加inst.text然后启动就能进入文本安装界面。3.5 文本安装openEuler的具体过程文本安装的过程和传统Linux安装器很像。几个重点步骤选择语言选English或中文随个人习惯我选了英文避免终端显示乱码。软件选择如果只需要最小环境选“Minimal Install”想顺手有个开发环境可以在安装源里勾选开发工具相关组件。动手学习者建议先最小安装后期再用dnf groupinstall补装更灵活。安装目的地选择刚才挂载的virtio磁盘分区方式勾选“Automatically configure partitioning”。初学者直接用自动分区不用手动折腾LVM。网络配置在用户态NAT下默认网卡会自动通过DHCP获取IP。我习惯在安装时把主机名设为arm64-lab方便后期识别。Root密码设置一个自己记得住的密码学习环境无所谓复杂度但别太简单导致被扫描到后出安全问题。安装过程要等一段时间。本地纯软件仿真下解包和写盘都会比真机慢可能要20到40分钟。这期间不需要盯着屏幕去做别的事就行。3.6 安装后的SSH登录安装完成后系统会提示重启。先把同一个QEMU命令行里的-cdrom参数去掉再重新启动虚拟机qemu-system-aarch64 \ -machine virt,gic-version3 \ -cpu cortex-a72 \ -smp 4 \ -m 4096 \ -bios /usr/share/qemu-efi-aarch64/QEMU_EFI.fd \ -drive fileopenEuler-arm64.qcow2,formatqcow2,ifvirtio \ -netdev user,idnet0,hostfwdtcp::2222-:22 \ -device virtio-net-pci,netdevnet0 \ -nographic系统起来后在宿主机上执行ssh rootlocalhost -p 2222这里之所以连localhost而不是虚拟机的IP是因为用户态网络栈对外不可见端口转发已经把虚拟机的22端口映射到了宿主机的2222端口。第一次连接会提示确认主机密钥输入yes后输入root密码就能进系统了。进入系统后先确认一下架构是不是aarch64uname -m输出是aarch64说明你已经跑在真实的AArch64用户态/内核环境中这一步的成就感还挺强。4. 虚拟机里的动手实践路线4.1 建立“架构意识”的基础命令先进系统后用几条命令建立架构意识uname -a lscpu cat /proc/cpuinfo | grep -E processor|CPU part|Featureslscpu会显示架构为AArch64字节序通常是小端。/proc/cpuinfo里的CPU part值能看出模拟的是哪个核心型号。如果你用-cpu max启动Features里还可能看到一批Armv8扩展指令集名字。这些命令的作用不是看一眼就完而是帮助你理解一个关键事实Linux内核和用户态软件在AArch64和x86_64上的行为有很多差异最典型的是指针大小相同但指令集不同、系统调用编号不同、结构体内存布局的对齐规则不同。这些差异会在编译和运行第三方软件时逐渐暴露出来。4.2 编译一个C程序并验证二进制格式在Arm64虚拟机里装好GCCdnf install -y gcc gcc-c make编辑一个最简单的C程序#include stdio.h int main(void) { printf(hello arm64\n); return 0; }编译并查看生成文件格式gcc -o hello hello.c file hello输出大致是hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked ...看到ARM aarch64就知道这个二进制是AArch64指令集的了。把它反汇编看看objdump -d hello | head -40你会发现指令助记符完全不同于x86_64那边的一套比如stp x29, x30, [sp, #-16]!这类常见栈帧指令这才是Arm64的独特味道。这一步对之前只在x86上写程序的人来说是建立差异感最直观的办法。4.3 交叉编译入门用x86机器编译arm64程序如果你希望在宿主机x86_64环境下直接编译出能在虚拟机里运行的arm64程序就需要一个交叉编译工具链。以Debian/Ubuntu为例sudo apt install gcc-aarch64-linux-gnu然后编译aarch64-linux-gnu-gcc -o hello-arm64 hello.c把hello-arm64通过scp复制到虚拟机里scp -P 2222 hello-arm64 rootlocalhost:/root/在虚拟机里执行./hello-arm64你会发现程序正常输出。交叉编译对于仓库里没有源码的封闭场景是必不可少的手段也是团队协作中常见的一个需求。很多人觉得交叉编译难其实难点多半在头文件、库依赖和链接路径上初学者从静态链接的hello world入手最稳妥。给编译命令加上-static就能避开动态库路径问题。4.4 用Podman/Docker体验aarch64容器镜像容器是验证架构亲和性最好的手段之一。openEuler的软件源里可能同时提供docker或podman我这次用的是dockerdnf install -y docker systemctl enable --now docker拉取一个arm64的测试镜像docker pull arm64v8/alpine:latest运行docker run --rm arm64v8/alpine uname -m输出会是aarch64。这里有一个细节值得注意如果你在x86_64的宿主机上拉取arm64镜像正常跑不起来除非配置了模拟器。但在arm64虚拟机里arm64镜像直接原生运行这就是“架构匹配”的含义。可以再用一个Nginx镜像做点更直观的实验docker run -d --name web -p 8080:80 arm64v8/nginx curl http://localhost:8080看到Nginx欢迎页之后说明你已经在完整的arm64环境里跑通了容器网络。这一步对将来排查“镜像架构不匹配导致启动失败”很有帮助。4.5 观察真实的包管理生态openEuler基于RPM生态包管理工具是dnf/yum。在学习过程中我建议把这些命令过一遍dnf repolist dnf group list dnf search nginx dnf install -y nginx dnf info nginx注意dnf repolist输出了哪些软件源以及某个软件包是否只有AArch64版本。你会发现绝大多数软件在AArch64上都有对应的rpm包但有些极个别闭源或未适配软件只有x86_64包这时候就需要自己编译或寻找替代方案。这正是软件迁移的真实经验来源。打开/usr/lib/systemd/system/下的systemd unit文件看看服务管理方式在Arm64平台上和x86_64也基本一致。systemctl这些命令放到真机上直接就能用学的东西不浪费。4.6 给学习环境补充调试工具一个合格的学习环境应该有调试工具。在虚拟机里执行dnf install -y gdb strace perf valgrind比如用gdb调试那段hello程序gdb -q ./hello start info registersinfo registers能看到x0-x30这些通用寄存器和x86_64的rax/rbx完全不同。用strace跟踪系统调用strace -f ./hello可以看到write、exit_group这些系统调用名称。虽然系统调用名称在不同架构下看起来相似但背后的编号和调用方式不同。这些细节对日后做底层性能分析和工作负载特性分析很有价值。5. 常见问题与避坑清单本地QEMU仿真AArch64虽然可行但坑也不少。我把整个过程遇到的高频问题整理成了一张速查表现象原因解决办法启动后黑屏无任何输出缺少AArch64 UEFI固件或路径不对安装qemu-efi-aarch64用dpkg/L查找固件路径启动命令加-bios安装器找不到磁盘磁盘设备类型选错用-device virtio-blk-pci或-drive ifvirtio确保内核有virtio驱动安装过程极慢图形界面卡死TCG软件仿真性能有限内存不足改用inst.text文本安装给虚拟机至少4G内存减少并发任务文本安装时分辨率/终端乱码语言或终端宽度问题安装语言选English终端窗口尽量拉大SSH连接被拒绝openEuler默认未启动sshd或防火墙拦截安装后systemctl enable --now sshd在QEMU命令里确保hostfwd端口映射正确ssh提示host key changed每次重建虚拟机后宿主机known_hosts还留着旧记录删除~/.ssh/known_hosts中localhost:2222对应的旧公钥docker镜像下载慢或一直失败镜像源问题或NAT网络限制配置容器镜像加速或换用更小的arm64v8/alpine镜像5.1 踩得最深的一个坑UEFI固件缺失导致黑屏第一次在没有UEFI固件的情况下启动虚拟机屏幕上什么都没有我以为镜像下载坏了后来才发现是缺少AArch64启动固件。这个坑几乎是每个新手都会碰到的。解决办法就是执行apt install qemu-efi-aarch64然后找准路径。不同宿主机版本里路径可能是/usr/share/qemu-efi-aarch64/QEMU_EFI.fd也可能是/usr/share/AAVMF/AAVMF_CODE.fd。用包管理器查一下文件归属即可。5.2 本地TCG模式下的性能边界TCG模式真的是又稳又慢。我在装系统时等了30多分钟编译GCC这类大项目时会明显感到CPU被占满但项目进度推进很慢。后来我把编译任务分批同时不要给虚拟机开太多并发编译作业。如果要跑大型软件编译建议转云端。不过学习用途下慢一点反而让人更关注编译输出和日志倒不是坏事。5.3 模拟环境的指令集边界用QEMU的TCG模式模拟的是Armv8基础指令集它支持AArch64的大部分常规指令但对于某些特定扩展的存在性和行为可能与真机不完全一致。比如你想看某个针对鲲鹏优化过的数学库跑出特定指令路径本地模拟不一定能复现这时候必须上真机或云上Arm实例。所以我的建议是本地虚拟机用来验证“能不能运行”但验证“优化效果”必须换环境。6. 这个学习方向能走多远6.1 openEuler在Arm64上的生态观察在虚拟机里待了一段时间后你会对openEuler的生态有一个更具体的认知。openEuler的软件源分很多类基础OS包、开发工具、数据库、虚拟化组件、云原生相关组件都能通过dnf直接安装。很多组件都有aarch64的rpm包说明Arm64生态的成熟度已经很高了。同时你也应该学会看一个软件包是不是原生AArch64的dnf list --showduplicates nginx rpm -qp --qf %{ARCH}\n nginx-*.rpm如果架构字段是aarch64那说明软件包是原生编译的。如果一个软件在源里只有x86_64那在Arm环境中就需要另想办法。这些经验在以后规划迁移方案时特别有用。6.2 从虚拟机到真机虚拟机环境跑顺之后下一步建议是找一台云上的Arm实例把同样的流程再走一遍。差别主要体现在云上实例有KVM硬件虚拟化编译和系统安装速度会快好几倍。CPU特性暴露得更完整能看到真实AArch64服务器的Features列表。网络模型更接近真实部署可以配置安全组、公网IP、多网卡适合练习服务部署。性能压测、高并发访问这些动作只能放到真机或云实例上做。从我个人的体验看先虚拟机后云端这个顺序比较符合学习规律。虚拟机能过滤掉一部分环境误操作带来的损失而云端实例能补充性能与真实硬件特性的短板。两者结合才能把“openEuler Arm64”这个方向学得比较扎实。6.3 扩展方向虚拟机里的openEuler Arm64环境还能用来做不少进阶实验搭一套Kubernetes集群观察AArch64节点上容器编排的表现。编译移植一些仅支持x86_64的开源项目改Makefile和CMakeLists为AArch64生成可运行版本。尝试构建自己的内核安装自编译的内核并启动。用perf采集程序的硬件计数器和软件事件分析AArch64下的性能profile。这些方向并不需要一开始就买真机虚拟机足够跑通。等真正理解了构建链、包管理和系统机制再上真机做性能层面的研究会顺畅很多。最后再说一个我自己的习惯每踩一个坑就重新建一次虚拟机把操作步骤整理成一段可复用的脚本。因为本地QEMU可以随时销毁重建所以这种反复打磨成本极低。等哪天真拿到了实机直接把积累的这些命令和配置带过去环境分分钟就能复现。学习Arm64 openEuler不需要等硬件到位才开始虚拟机就是最好的第一个训练场。