QEMU 11.1.2 多架构编译指南(x86_64 / aarch64 / riscv64)

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)。两个理由:

  1. 防止误伤系统组件 :/usr/local 是系统级目录,由发行版和系统管理员管理,往里面写东西(尤其是误操作 rm -rf)可能破坏系统原有组件;卸载也只需删掉一个目录,干净利落。
  2. 避免同名工具互相覆盖 :三个架构各有一套 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. 下一步学习建议

  1. 交叉编译 + 用户模式实验 :装 gcc-aarch64-linux-gnu 或 gcc-riscv64-linux-gnu,写个 hello world 交叉编译后用 qemu-aarch64 ./a.out 直接运行,理解用户模式的价值。
  2. 阅读官方文档 :源码树里 docs/ 目录和 qemu/docs/about/ 有大量文档;qemu-system-xxx -machine help、-cpu help 能列出当前支持的机器和 CPU 模型。
  3. 加打印调代码 :在源码里随便加个 printf(QEMU 内部惯用 qemu_log / error_report),增量 ninja 后跑起来看输出,是熟悉 QEMU 代码结构最快的路径。
  4. 挂 gdb 调 QEMU 本身 :启动 QEMU 时加 -S -gdb tcp::1234,用宿主机 gdb 远程调试虚拟机内部,或直接 gdb --args qemu-system-aarch64 ... 调 QEMU 进程。
相关推荐
道满科技2 小时前
Function Calling 参数总出错?只修参数就行
linux·windows·python
Gauss松鼠会3 小时前
高斯数据库(openGauss)的物理存储结构
运维·服务器·数据库·sql·opengauss·经验总结
Android系统攻城狮3 小时前
Linux Gstreamer深度解析之gst_audio_ring_buffer_prepare_read调用流程与实战(七十四)
linux·运维·服务器·gstreamer音视频·音视频进阶
杨云龙UP3 小时前
PostgreSQL 18.4 数据库整库迁移实战:pg_dump 备份与 pg_restore 恢复完整操作手册
运维·服务器·数据库·postgresql·pg_dump·pg_restore
我科绝伦(Huanhuan Zhou)3 小时前
Linux 通用压力测试脚本合集:磁盘 + 网络
linux·网络·压力测试
土星云SaturnCloud5 小时前
土星云智慧服务区方案:AI视觉+边缘计算+视频存管一体化的全场景智能化落地解析
服务器·人工智能·ai·边缘计算
Soneswy6 小时前
Linux系统篇--重定向与文件操作命令
linux
有毒的教程6 小时前
Linux底层存储路由配
linux·运维·网络
liqingdi4376 小时前
[SWPUCTF 2023 秋季新生赛]Easy SMC
服务器·前端·javascript