QEMU 11.1.2 多架构编译指南(x86_64 / aarch64 / riscv64)
本文记录在一台 Ubuntu 22.04 x86_64 主机上,从同一份 QEMU 11.1.2 源码编译出
x86_64、aarch64、riscv64 三种目标架构(系统模式 + 用户模式)的完整过程,
供初学者照抄实践。
编译时间参考:每个架构约 5~10 分钟(取决于 CPU 核数),三个架构合计约 20~30 分钟。
1. 先搞懂几个概念
1.1 什么是"目标架构"(target)
QEMU 是模拟器,它自己运行在某个 CPU 上(叫宿主机 ,host),模拟出来的 CPU 叫目标机(target,guest)。我们想在 x86_64 服务器上模拟 ARM64 和 RISC-V 64 位机器,所以需要编译三个 target:
x86_64------ 和宿主机相同架构(常用于开发调试 QEMU 本身,或跑 KVM 加速虚拟机)aarch64------ ARM 64 位,服务器 / 嵌入式 / 苹果 M 系列同款架构riscv64------ RISC-V 64 位,开源指令集架构
1.2 什么是"系统模式"和"用户模式"
每个 target 有两种编译产物,名字规则一目了然:
| 产物名 | 模式 | 作用 |
|---|---|---|
qemu-system-xxx |
系统模式(softmmu) | 模拟一整台机器(CPU、内存、中断控制器、磁盘、网卡......),可以启动完整的 Linux 内核 |
qemu-xxx |
用户模式(linux-user) | 只模拟 CPU,直接在宿主 Linux 上运行单个目标架构的可执行文件(交叉编译出的程序不用装目标系统就能跑) |
所以 configure 时每个架构传两个 target:
bash
--target-list=x86_64-softmmu,x86_64-linux-user
--target-list=aarch64-softmmu,aarch64-linux-user
--target-list=riscv64-softmmu,riscv64-linux-user
1.3 构建系统分层
QEMU 的构建系统是三层洋葱,初学者只需要记住"configure 只跑一次,日常用 ninja":
#mermaid-svg-bYutk46CXdvS03pC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bYutk46CXdvS03pC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bYutk46CXdvS03pC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bYutk46CXdvS03pC .error-icon{fill:#552222;}#mermaid-svg-bYutk46CXdvS03pC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bYutk46CXdvS03pC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bYutk46CXdvS03pC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bYutk46CXdvS03pC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bYutk46CXdvS03pC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bYutk46CXdvS03pC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bYutk46CXdvS03pC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bYutk46CXdvS03pC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bYutk46CXdvS03pC .marker.cross{stroke:#333333;}#mermaid-svg-bYutk46CXdvS03pC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bYutk46CXdvS03pC p{margin:0;}#mermaid-svg-bYutk46CXdvS03pC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bYutk46CXdvS03pC .cluster-label text{fill:#333;}#mermaid-svg-bYutk46CXdvS03pC .cluster-label span{color:#333;}#mermaid-svg-bYutk46CXdvS03pC .cluster-label span p{background-color:transparent;}#mermaid-svg-bYutk46CXdvS03pC .label text,#mermaid-svg-bYutk46CXdvS03pC span{fill:#333;color:#333;}#mermaid-svg-bYutk46CXdvS03pC .node rect,#mermaid-svg-bYutk46CXdvS03pC .node circle,#mermaid-svg-bYutk46CXdvS03pC .node ellipse,#mermaid-svg-bYutk46CXdvS03pC .node polygon,#mermaid-svg-bYutk46CXdvS03pC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bYutk46CXdvS03pC .rough-node .label text,#mermaid-svg-bYutk46CXdvS03pC .node .label text,#mermaid-svg-bYutk46CXdvS03pC .image-shape .label,#mermaid-svg-bYutk46CXdvS03pC .icon-shape .label{text-anchor:middle;}#mermaid-svg-bYutk46CXdvS03pC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bYutk46CXdvS03pC .rough-node .label,#mermaid-svg-bYutk46CXdvS03pC .node .label,#mermaid-svg-bYutk46CXdvS03pC .image-shape .label,#mermaid-svg-bYutk46CXdvS03pC .icon-shape .label{text-align:center;}#mermaid-svg-bYutk46CXdvS03pC .node.clickable{cursor:pointer;}#mermaid-svg-bYutk46CXdvS03pC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bYutk46CXdvS03pC .arrowheadPath{fill:#333333;}#mermaid-svg-bYutk46CXdvS03pC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bYutk46CXdvS03pC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bYutk46CXdvS03pC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bYutk46CXdvS03pC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bYutk46CXdvS03pC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bYutk46CXdvS03pC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bYutk46CXdvS03pC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bYutk46CXdvS03pC .cluster text{fill:#333;}#mermaid-svg-bYutk46CXdvS03pC .cluster span{color:#333;}#mermaid-svg-bYutk46CXdvS03pC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bYutk46CXdvS03pC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bYutk46CXdvS03pC rect.text{fill:none;stroke-width:0;}#mermaid-svg-bYutk46CXdvS03pC .icon-shape,#mermaid-svg-bYutk46CXdvS03pC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bYutk46CXdvS03pC .icon-shape p,#mermaid-svg-bYutk46CXdvS03pC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bYutk46CXdvS03pC .icon-shape .label rect,#mermaid-svg-bYutk46CXdvS03pC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bYutk46CXdvS03pC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bYutk46CXdvS03pC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bYutk46CXdvS03pC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 探测依赖、生成配置
生成构建规则
实际编译
ninja install
configure 脚本
meson
build.ninja
ninja 并行构建
可执行文件
安装到 prefix 目录
1.4 为什么要用独立构建目录(out-of-tree build)
QEMU 支持多个构建目录共用一份源码。我们为每个 target 建一个目录:
/root/qemu/ ← 源码目录(保持干净)
├── build/qemu-x86_64/ ← x86_64 的编译产物
├── build/qemu-aarch64/ ← aarch64 的编译产物
└── build/qemu-riscv64/ ← riscv64 的编译产物
/root/qemu-install/ ← 安装目录(用户自定义,不碰系统目录)
├── qemu-x86_64/ ← x86_64 的 bin、share 等
├── qemu-aarch64/ ← aarch64 的 bin、share 等
└── qemu-riscv64/ ← riscv64 的 bin、share 等
好处:三个架构互不干扰,删掉任一目录即可重编该架构,源码目录不受污染。
2. 环境准备:安装依赖
2.1 编译工具和开发库
bash
apt-get update
apt-get install -y \
git build-essential gcc \ # 编译器
ninja-build meson \ # 构建系统(QEMU 用 meson + ninja,不用 autotools)
python3 python3-venv \ # QEMU 构建脚本依赖 Python
flex bison \ # 语法生成器
pkg-config libglib2.0-dev \ # glib:QEMU 核心依赖
libpixman-1-dev \ # pixman:图形渲染依赖
libslirp0 libslirp-dev \ # slirp:user 模式网络后端(-netdev user 需要)
zlib1g-dev
本次实际环境是 Ubuntu 22.04,预先已有 gcc 11.4、ninja 1.10、glib 和 pixman 的开发包,单独补装了 meson:
bash
apt-get install -y meson
2.2 验证依赖
bash
gcc --version | head -1 # gcc (Ubuntu 11.4.0) 11.4.0
meson --version # 0.61.2
ninja --version # 1.10.1
小贴士:
QEMU 11.x 对 meson 版本有下限要求,Ubuntu 22.04 仓库里的 0.61 即可满足。
如果 configure 报某个库缺失,按提示
apt-get install对应的-dev包再重跑即可,configure 失败不会产生残留副作用。
3. 编译三个架构
以下命令假设源码在 /root/qemu,全部命令逐条可复制执行。
为什么 prefix 指向
/root/qemu-install/而不是/usr/local?
/usr/local是系统目录,初学者误操作可能破坏系统原有组件,且多架构的同名工具(qemu-img等)会互相覆盖。用户自定义目录天然隔离、删目录即卸载、随时可重建。普通用户请把
/root换成$HOME。详见 FAQ Q5。
3.1 x86_64
bash
# 0) 建安装总目录(一次即可)
mkdir -p /root/qemu-install
# 1) 建独立构建目录(configure 也可以自动建,显式创建更清晰)
mkdir -p build/qemu-x86_64
cd build/qemu-x86_64
# 2) 配置:目标、安装前缀
/root/qemu/configure \
--target-list=x86_64-softmmu,x86_64-linux-user \
--prefix=/root/qemu-install/qemu-x86_64
# 3) 编译(ninja 自动并行,无需 -j)
ninja
# 4) 安装
ninja install
3.2 aarch64
bash
mkdir -p build/qemu-aarch64
cd build/qemu-aarch64
/root/qemu/configure \
--target-list=aarch64-softmmu,aarch64-linux-user \
--prefix=/root/qemu-install/qemu-aarch64
ninja
ninja install
3.3 riscv64
bash
mkdir -p build/qemu-riscv64
cd build/qemu-riscv64
/root/qemu/configure \
--target-list=riscv64-softmmu,riscv64-linux-user \
--prefix=/root/qemu-install/qemu-riscv64
ninja
ninja install
3.4 configure 输出怎么读
configure 成束时会打印一张选项摘要表("User defined options"往上都是),重点确认:
prefix------ 安装目录,就是你传的--prefix- 没有
ERROR字样即成功;最后会提示Found ninja-1.10.1 at /usr/bin/ninja - 可选功能(如
slirp、sdl、gtk)显示 NO 不影响基本功能,只是相应高级特性不可用
如果想看全量探测结果,构建目录里有 config.log 和 meson 配置摘要可查。
3.5 常用 configure 选项速查
| 选项 | 作用 | 建议 |
|---|---|---|
--target-list=... |
要编译的目标架构列表 | 见 1.2 节解释;不传则编译全部 target(非常慢) |
--prefix=... |
ninja install 的安装根目录 |
建议每种架构独立目录,方便管理 |
--static |
静态链接(linux-user 常用) | 想把二进制拷到别的机器跑时再加 |
--disable-docs |
不编译文档 | 只想要二进制时省时间 |
4. 验证编译结果
4.1 版本检查
bash
# 源码目录内直接运行
/root/qemu/build/qemu-x86_64/qemu-system-x86_64 --version
/root/qemu/build/qemu-aarch64/qemu-system-aarch64 --version
/root/qemu/build/qemu-riscv64/qemu-system-riscv64 --version
# 安装目录内运行
/root/qemu-install/qemu-x86_64/bin/qemu-system-x86_64 --version
/root/qemu-install/qemu-aarch64/bin/qemu-system-aarch64 --version
/root/qemu-install/qemu-riscv64/bin/qemu-system-riscv64 --version
预期输出全部为:
QEMU emulator version 11.1.2 (v11.1.2)
4.2 确认产物齐全
| 安装目录 | 系统模式 | 用户模式 | 工具类 |
|---|---|---|---|
/root/qemu-install/qemu-x86_64/bin |
qemu-system-x86_64 |
qemu-x86_64 |
qemu-img、qemu-io、qemu-nbd 等 |
/root/qemu-install/qemu-aarch64/bin |
qemu-system-aarch64 |
qemu-aarch64 |
同上 |
/root/qemu-install/qemu-riscv64/bin |
qemu-system-riscv64 |
qemu-riscv64 |
同上 |
4.3 最低限度跑一下
系统模式不带磁盘也能进"无盘启动"(能看到 vzloader / OpenBIOS 输出即说明模拟器可用):
bash
/root/qemu-install/qemu-aarch64/bin/qemu-system-aarch64 -M virt -cpu cortex-a57 -nographic
# 退出:按 Ctrl-A 再按 x
用户模式需要目标架构的可执行文件才能测,参考第 6 节。
5. 常见问题(FAQ)
Q1:configure 报错 "meson not found" 或 meson 版本太低?
Ubuntu 22.04 直接 apt-get install -y meson(0.61 可用)。更老的发行版建议 pip3 install meson 装新版,或先升级系统。
Q2:想改代码后重新编译?
源码只改 /root/qemu 源码目录里的文件(构建目录里只有编译产物,没有源码副本),然后到对应架构的构建目录 重跑 ninja,增量编译通常几十秒完成:
- 改了 riscv 专属代码(如
target/riscv/)→ 去build/qemu-riscv64跑ninja(在别的构建目录跑不会报错,但改动不会被编进去)。 - 改了通用代码(如
accel/tcg/、block/)→ 三个构建目录都要各跑一次ninja。
Q3:想完全重编某个架构?
删掉对应的 build/qemu-XXX 目录,重跑第 3 节流程即可,源码不受影响。
Q4:磁盘空间不够?
每个构建目录全量约 1~2 GB。三个架构只共用源码、不共用产物,机空间紧张时可以编一个删一个构建目录(安装目录 /root/qemu-install/qemu-XXX 里的二进制是拷贝出来的,删除构建目录不影响使用)。
Q5:--prefix 装到哪个目录?为什么不装 /usr 或 /usr/local?
初学者强烈建议装到用户自定义目录 (本文用 /root/qemu-install/,普通用户可换成 $HOME/qemu-install)。两个理由:
- 防止误伤系统组件 :
/usr/local是系统级目录,由发行版和系统管理员管理,往里面写东西(尤其是误操作rm -rf)可能破坏系统原有组件;卸载也只需删掉一个目录,干净利落。 - 避免同名工具互相覆盖 :三个架构各有一套
qemu-img、qemu-nbd等同名工具,装进系统目录会互相覆盖。独立 prefix(本文方案)天然隔离。
想敲 qemu-system-aarch64 不带全路径时,把对应 bin 目录加进 PATH 即可:
bash
export PATH=/root/qemu-install/qemu-aarch64/bin:$PATH # 可写进 ~/.bashrc
6. 启动完整 Ubuntu 系统(实操)
本节用 Ubuntu 24.04(noble)官方云镜像,在刚编好的 QEMU 上启动三个架构的完整系统。云镜像是"预装好的磁盘",免安装、开机即用,唯一门槛是它默认没有登录密码------需要一小段 cloud-init 配置注入 SSH 公钥。以下所有命令均实际验证通过,每条可复制执行。
6.0 前置检查:user 网络后端
本文用 -netdev user(用户态网络,无需 root 和额外配置),它依赖 libslirp (QEMU 依赖安装清单里已包含它)。如果编译时没有这个库,configure 会静默跳过该功能,启动虚拟机时会报 network backend 'user' is not compiled into this binary。确认装了它:
bash
apt-get install -y libslirp0 libslirp-dev
验证已编入:/root/qemu-install/qemu-XXX/bin/qemu-system-XXX -netdev help | grep user 有输出即可。
6.1 下载三个架构的云镜像
bash
mkdir -p /root/vm-images && cd /root/vm-images
wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img # x86_64,约 600 MB
wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-arm64.img # aarch64,约 600 MB
wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-riscv64.img # riscv64,约 800 MB
三个镜像合计约 2 GB。注意:镜像里的内核和用户态二进制都是架构专属的,三份不能混用------这正是需要分别下载三份的原因。
6.2 制作 cloud-init 种子盘(注入 SSH 公钥)
云镜像第一次开机时,cloud-init 会从"种子盘"(一个 NoCloud 格式的小 ISO)读取登录配置。制作一次,三个架构通用:
bash
cd /root/vm-images
# 1) 生成 SSH 密钥(若已有则跳过)
[ -f ~/.ssh/id_ed25519 ] || ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519 -q
# 2) 写 user-data:给默认用户 ubuntu 注入公钥
# (不写 users: 列表,镜像自带的默认用户模板就会照常生效------
# 该模板已内置免密 sudo 和 bash,直接用即可)
cat > user-data <<'EOF'
#cloud-config
ssh_pwauth: false
ssh_authorized_keys:
- __PUBKEY__
EOF
sed -i "s|__PUBKEY__|$(cat ~/.ssh/id_ed25519.pub)|" user-data
# 3) 写 meta-data(NoCloud 数据源要求这个文件存在)
cat > meta-data <<'EOF'
instance-id: vm-001
local-hostname: ubuntu-vm
EOF
# 4) 打包成种子 ISO(需要 genisoimage:apt-get install -y genisoimage)
genisoimage -output seed.iso -volid cidata -joliet -rock user-data meta-data
一个要点:改了 user-data 想让它重新生效 ,把 meta-data 里的 instance-id 换成新值(如 vm-002)后重新生成种子盘、重启虚拟机;想完全重置虚拟机,重新 wget 镜像最干净。
6.3 启动 x86_64(默认开 KVM)
bash
cd /root/vm-images
/root/qemu-install/qemu-x86_64/bin/qemu-system-x86_64 \
-M q35 -accel kvm -cpu host -m 2G -smp 2 \
-drive file=noble-server-cloudimg-amd64.img,format=qcow2,if=virtio \
-drive file=seed.iso,format=raw,if=virtio,readonly=on \
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
-device virtio-net-pci,netdev=net0 \
-nographic
x86_64 机器自带 SeaBIOS,从 virtio 磁盘引导无需额外固件。-M q35 显式指定较新的机型(见下文),这样三个架构的启动命令都显式写明了 machine 和 cpu,命令即文档。
KVM vs TCG------什么时候能用 KVM? 这是理解 QEMU 两种执行模式的好机会:
| TCG(默认) | KVM | |
|---|---|---|
| 原理 | 软件逐条翻译并模拟 guest 指令 | guest 指令直接跑在 CPU 上,由 CPU 硬件虚拟化支持 |
| 性能 | 慢(约为原生的 1/5~1/20) | 接近原生 |
| 跨架构模拟 | ✅ 可以(x86 宿主机跑 ARM guest) | ❌ 不行 |
| 前提条件 | 无 | ① 宿主机 CPU 支持硬件虚拟化(Intel VT-x/AMD-V)② 内核模块已加载(/dev/kvm 存在)③ guest 架构与宿主机相同 |
只有 x86_64 虚拟机能开 KVM------因为宿主机是 x86_64,KVM 是"真 CPU 直接执行",CPU 不认识 ARM 指令就没法加速;aarch64/riscv64 虚拟机只能走 TCG(这正是 6.4/6.5 节启动慢的原因)。判断方法:
bash
ls -la /dev/kvm # 存在 → KVM 可用(内核模块已加载)
grep -cE "vmx|svm" /proc/cpuinfo # 非 0 → CPU 支持 VT-x/AMD-V(Intel 是 vmx,AMD 是 svm)
本文实测环境:宿主机 AMD EPYC(svm 标志),q35 + KVM 启动 x86_64 虚拟机约 45 秒 SSH 就绪,-cpu host 让 guest 直接看到宿主真实 CPU 型号(AMD EPYC 9555);纯 TCG 首启约 100 秒。KVM 不可用时(如某些云容器环境无 /dev/kvm),把 -accel kvm -cpu host 换成 -accel tcg 即可回退。
关于 q35 机型 :-M q35 是比默认 pc(i440FX + PIIX,1996)新 的机型------"Standard PC (Q35 + ICH9, 2009)",芯片组从 i440FX 换成 Intel Q35:提供 PCIe 总线(而非老 PCI)、ICH9 南桥、SATA 原生支持(老机型是模拟 IDE)、更现代的电源管理,现代 Linux guest 推荐使用。没有特殊需求时用默认 pc 也可以,两种机型本文云镜像都能正常跑。
关于 -accel kvm :老写法 -enable-kvm 等价于 -accel kvm(前者只是后者的便捷别名),推荐统一用 -accel,因为它更通用------支持回退链(-accel kvm -accel tcg,KVM 不可用时自动回退 TCG)和加速器子参数(如 -accel tcg,thread=multi)。
6.4 启动 aarch64(需要显式挂 UEFI 固件)
aarch64 的 virt 板不带默认固件,必须显式挂 UEFI 固件(edk2)才能引导磁盘。code 只读共享安装目录那份,vars(可写的配置盘)每台虚拟机从安装目录拷一份独立的:
bash
cd /root/vm-images
cp /root/qemu-install/qemu-aarch64/share/qemu/edk2-arm-vars.fd ./vars-arm64.fd
/root/qemu-install/qemu-aarch64/bin/qemu-system-aarch64 \
-M virt -cpu cortex-a57 -smp 2 -m 2G \
-drive if=pflash,format=raw,file=/root/qemu-install/qemu-aarch64/share/qemu/edk2-aarch64-code.fd,readonly=on \
-drive if=pflash,format=raw,file=/root/vm-images/vars-arm64.fd \
-drive file=noble-server-cloudimg-arm64.img,format=qcow2,if=virtio \
-drive file=seed.iso,format=raw,if=virtio,readonly=on \
-netdev user,id=net0,hostfwd=tcp::2223-:22 \
-device virtio-net-device,netdev=net0 \
-nographic
两个固件文件的角色一句话记住:code 是固件程序本体(只读,写在命令行第 1 个 pflash 处),vars 是固件的配置存储(可写,第 2 个),顺序不能反。aarch64 和 riscv64 机制完全相同。
TCG 下首启较慢:UEFI 加内核约 1 分钟,cloud-init 完成约 2 分钟,SSH 才可达------期间控制台看似安静属正常,耐心等。
6.5 启动 riscv64(同样需要 edk2 pflash)
与 aarch64 同理,挂法相同:
bash
cd /root/vm-images
cp /root/qemu-install/qemu-riscv64/share/qemu/edk2-riscv-vars.fd ./vars-riscv64.fd
/root/qemu-install/qemu-riscv64/bin/qemu-system-riscv64 \
-M virt -smp 2 -m 2G \
-drive if=pflash,format=raw,file=/root/qemu-install/qemu-riscv64/share/qemu/edk2-riscv-code.fd,readonly=on \
-drive if=pflash,format=raw,file=/root/vm-images/vars-riscv64.fd \
-drive file=noble-server-cloudimg-riscv64.img,format=qcow2,if=virtio \
-drive file=seed.iso,format=raw,if=virtio,readonly=on \
-netdev user,id=net0,hostfwd=tcp::2224-:22 \
-device virtio-net-device,netdev=net0 \
-nographic
启动顺序为 OpenSBI(QEMU 内置)→ edk2 UEFI → grub → 内核。riscv64 的 TCG 模拟比 aarch64 更慢,从加电到 SSH 可达实测约 5~8 分钟,属正常现象。
6.6 登录、退出与验证
看到登录提示符后(首启会先跑 cloud-init),另开一个终端 SSH 登录:
bash
ssh -p 2222 ubuntu@127.0.0.1 # x86_64 虚拟机
ssh -p 2223 ubuntu@127.0.0.1 # aarch64 虚拟机
ssh -p 2224 ubuntu@127.0.0.1 # riscv64 虚拟机
验证确实是三个不同架构的系统(本文实测输出附在注释里):
bash
uname -m # 依次输出:x86_64 / aarch64 / riscv64
退出 -nographic 前台控制台 :按 Ctrl-A 松开再按 x(不是 Ctrl-C-------nographic 下 Ctrl-C 会传给虚拟机)。
sudo 免密 :cloud 镜像的 ubuntu 用户默认有免密 sudo,登进去 sudo -i 即可变 root。
后台运行技巧 :不想占一个前台终端时,把 -nographic 换成 -serial file:console.log -display none,再用 nohup ... & 拉起,日志(含内核 dmesg)都会写进 console.log,排查启动问题非常有用。
6.7 排障手段:QEMU monitor
虚拟机"好像卡住"时(串口无输出、SSH 不通),先别急着杀进程,挂个 monitor 看内部状态:
bash
# 启动时加一个 monitor socket:
# -monitor unix:/tmp/vm-mon.sock,server,nowait
# 然后随时连上去查看(需要 socat:apt-get install -y socat)
echo "info status" | socat - unix-connect:/tmp/vm-mon.sock # VM 状态
echo "info registers" | socat - unix-connect:/tmp/vm-mon.sock # vCPU 寄存器(PC 停在 0x200 = 固件没加载)
echo "info block" | socat - unix-connect:/tmp/vm-mon.sock # 磁盘挂载
本文定位 aarch64 无法启动就是靠 info registers 看到 PC=0x200、通用寄存器全零,判断出 vCPU 根本没执行固件。
6.8 参数解释速查
| 参数 | 含义 |
|---|---|
-M virt |
QEMU "virt" 通用虚拟化平台(aarch64/riscv64 推荐机型) |
-cpu cortex-a57 |
模拟一颗具体的 ARM Cortex-A57 CPU(virt 机 riscv64 无需指定) |
-drive if=pflash,... |
挂 UEFI 固件:code 只读、vars 可写,必须成对出现(aarch64/riscv64 必需,x86_64 不需要) |
-m 2G / -smp 2 |
内存 / 虚拟 CPU 数量 |
-drive file=...,if=virtio |
挂 qcow2 磁盘走 virtio 总线(性能远好于模拟 IDE) |
-netdev user,...,hostfwd=tcp::2222-:22 |
用户态网络(需 libslirp),宿主机 2222 端口转发到虚拟机 22(SSH) |
-nographic |
不开图形窗口,串口当控制台 |
-monitor unix:... |
挂一个 Unix socket 的 QEMU monitor,用于运行中排障 |
6.8.1 为什么有的参数能省略?------默认值机制
QEMU 的每个参数背后都可能藏着一个默认值,而默认值未必符合你的场景。三个架构的差异正好说明这一点:
| 疑问 | 答案 | 源码出处 |
|---|---|---|
x86_64 有默认 -M |
默认机器 pc(i440FX 芯片组)被打了 is_default 标志,不指定时 QEMU 自动选它(本文 6.3 节显式指定了更新的 q35) |
hw/i386/pc_piix.c(DEFINE_I440FX_MACHINE_AS_LATEST 宏置 is_default = true) |
riscv64 不用 -cpu |
virt 板的默认 CPU 是 64 位通用 RV64(TYPE_RISCV_CPU_BASE),直接能跑 64 位系统 |
hw/riscv/virt.c:1726(mc->default_cpu_type = TYPE_RISCV_CPU_BASE) |
aarch64 要指定 -cpu |
virt 板有 默认值,但 TCG 下默认是 cortex-a15------一款 32 位 CPU,跑不了 64 位 Ubuntu 内核,所以要显式指定 64 位的 cortex-a57 |
hw/arm/virt.c:4122(virt_get_default_cpu_type 返回 cortex-a15) |
默认机器的选择逻辑 在 system/vl.c 的 find_default_machine():不指定 -M 时遍历该 target 的所有机器类,找 mc->is_default == true 的那个;一个都没有就报错退出 。x86 PC 是事实标准且机型单一,QEMU 给它设了默认;aarch64/riscv64 的机型多样(virt、sbsa-ref......),替用户默认反而不合适,所以必须显式 -M virt。
顺带解释 x86_64 的"最省事"现象 :x86_64 不但默认机器是 pc,该机器还带了默认固件(机器选项里写死 firmware=bios-256k.bin,即 SeaBIOS)------两层默认叠加,所以连 UEFI pflash 都不用挂。而 aarch64/riscv64 的 virt 板没有默认固件,必须手动成对挂 edk2 固件(见 6.4/6.5 节)。
实践建议:想知道自己的 target 有哪些选择、哪个是默认的,用两个帮助命令:
bash
/root/qemu-install/qemu-aarch64/bin/qemu-system-aarch64 -machine help # 列出所有机器,默认的标注 (default)
/root/qemu-install/qemu-aarch64/bin/qemu-system-aarch64 -cpu help # 列出该机器支持的 CPU 型号
初学阶段建议像本文一样把关键参数显式写全(哪怕与默认值相同),命令即文档,换机器执行也不怕默认值不同。
6.9 磁盘占用与清理(实测数据)
| 项目 | 大小 |
|---|---|
| 三个云镜像(qcow2 下载态) | 合计约 2.1 GB(600+600+800 MB) |
| 两份 UEFI vars 盘 | 约 100 MB |
| 每个虚拟机轻量使用后(系统更新、包安装后增长) | 约 2~3 GB |
| 建议预留总空间 | 10~15 GB(本文实测当前仅占 2.2 GB) |
清理:虚拟机停掉后直接删对应的 .img 和 vars-*.fd 即可;想重装就重新 wget 镜像(种子盘 seed.iso 可复用)。
6.10 进阶:用快照保护镜像
直接在 cloud 镜像上跑,所有改动会写进原镜像。想保留"出厂状态"方便回滚,用 qcow2 backing file 机制(增量写到新盘,不碰原镜像):
bash
# 以原镜像为底建一个增量盘
/root/qemu-install/qemu-aarch64/bin/qemu-img create -f qcow2 -F qcow2 \
-b noble-server-cloudimg-arm64.img arm64-run.img
# 启动命令里把 arm64-run.img 换掉原镜像即可
# 想重置虚拟机状态:删掉 arm64-run.img 重建即可,原镜像始终干净
7. 下一步学习建议
- 交叉编译 + 用户模式实验 :装
gcc-aarch64-linux-gnu或gcc-riscv64-linux-gnu,写个 hello world 交叉编译后用qemu-aarch64 ./a.out直接运行,理解用户模式的价值。 - 阅读官方文档 :源码树里
docs/目录和qemu/docs/about/有大量文档;qemu-system-xxx -machine help、-cpu help能列出当前支持的机器和 CPU 模型。 - 加打印调代码 :在源码里随便加个
printf(QEMU 内部惯用qemu_log/error_report),增量ninja后跑起来看输出,是熟悉 QEMU 代码结构最快的路径。 - 挂 gdb 调 QEMU 本身 :启动 QEMU 时加
-S -gdb tcp::1234,用宿主机 gdb 远程调试虚拟机内部,或直接gdb --args qemu-system-aarch64 ...调 QEMU 进程。