2. QEMU RISC-V 环境搭建:从源码到第一个虚拟机

环境搭建这事儿,说简单也简单,说坑也多。我见过不少同事卡在编译环节,一卡就是半天。其实只要理清几个关键点,整个过程挺顺畅的。这一章咱们就把 QEMU RISC-V 的环境从头搭一遍,从源码编译到启动第一个虚拟机,一步不落。

2.1 源码编译与安装

我个人习惯用源码编译,而不是直接装包管理器里的版本。为什么?因为调试的时候需要打开一些额外的编译选项,比如 --enable-debug,包管理器给的二进制通常不带这些。

2.1.1 获取源码

git clone https://gitlab.com/qemu-project/qemu.git
cd qemu
git checkout stable-8.2  # 我建议用稳定分支,别追 master

我曾经追过一次 master,结果某个提交把 RISC-V 的 PMP 逻辑改坏了,排查了两天才发现是上游的临时问题。所以,除非你要给 QEMU 提 patch,否则老老实实用 stable。

2.1.2 配置与编译

QEMU 支持两种模式:系统模式(system mode)和用户模式(user mode)。系统模式模拟完整机器,能跑操作系统;用户模式只模拟用户态指令,适合跑单个程序。咱们做性能分析和调试,两种都得编译。

mkdir build && cd build

# 配置系统模式 + 用户模式,目标架构只选 riscv64
../configure \
  --target-list=riscv64-softmmu,riscv64-linux-user \
  --enable-debug \
  --enable-kvm \
  --enable-virtfs \
  --prefix=/opt/qemu-riscv

make -j$(nproc)
make install
我的建议: --enable-debug 会打开 QEMU 内部的断言和日志,性能会降一点,但调试时能省大量时间。生产环境可以去掉这个选项。

编译时间取决于机器。我的一台 8 核笔记本,大概 15 分钟能编完。如果遇到依赖缺失,比如 glib2、pixman、libfdt,用包管理器装上就行。

2.1.3 验证安装

/opt/qemu-riscv/bin/qemu-system-riscv64 --version
/opt/qemu-riscv/bin/qemu-riscv64 --version

能看到版本号,说明编译安装成功了。嗯,这里要注意:qemu-system-riscv64 是系统模式,qemu-riscv64 是用户模式,别搞混。

2.2 系统模式与用户模式配置

这两种模式的区别,说白了就是「模拟一台电脑」和「模拟一个程序」。我画了张图,帮你快速理解它们的定位。

QEMU RISC-V 两种运行模式 系统模式 (System Mode) • 模拟完整硬件:CPU、内存、外设 • 可以运行完整操作系统 • 用于内核开发、驱动调试 • 二进制:qemu-system-riscv64 用户模式 (User Mode) • 只模拟用户态指令 • 直接运行 ELF 文件 • 用于性能分析、指令集验证 • 二进制:qemu-riscv64 典型场景 • 启动 Linux 内核 + rootfs • 调试 U-Boot、OpenSBI • 测试设备驱动 典型场景 • 运行 RISC-V 交叉编译的程序 • 指令集兼容性测试 • 性能 Profiling 两种模式共享同一套 TCG 动态翻译引擎,但模拟的硬件范围不同

你想想看,如果你只是想跑一个 RISC-V 的 Hello World,用用户模式就够了,几毫秒就出结果。但如果你要调试 Linux 内核的启动流程,那就必须用系统模式。

2.3 常用启动参数详解

QEMU 的参数非常多,但常用的也就十几个。我把它们分成三类,列了个表格,方便你查阅。

类别 参数 说明 示例
机器配置 -machine 指定机器类型,RISC-V 常用 virt -machine virt
-cpu CPU 型号,可选 rv64、sifive-u54 等 -cpu rv64
-smp 模拟的 CPU 核心数 -smp 4
内存与存储 -m 内存大小,支持单位 G/M -m 2G
-kernel 指定内核镜像文件 -kernel vmlinux
-drive 挂载磁盘或 rootfs 镜像 -drive file=rootfs.ext4,format=raw
调试与监控 -s 等价于 -gdb tcp::1234,开启 GDB 调试端口 -s
-S 启动时暂停 CPU,等待 GDB 连接 -S
-d 打开 QEMU 内部日志,如 in_asm、cpu、int -d in_asm,cpu
避坑提醒: 我曾经在 -d 参数里同时开了 in_asm 和 int,结果日志刷屏,虚拟机直接卡死。建议调试时只开需要的日志类别,别贪多。

2.4 第一个 RISC-V 虚拟机启动

理论说完了,咱们动手。启动一个 RISC-V 虚拟机需要三样东西:OpenSBI(机器模式固件)、Linux 内核、rootfs(根文件系统)。

2.4.1 准备启动文件

我建议直接用 Buildroot 生成这三样,省心。当然你也可以从发行版拿现成的,比如 Fedora RISC-V 的镜像。

# 用 Buildroot 快速生成(我常用的方式)
git clone https://git.buildroot.net/buildroot
cd buildroot
make qemu_riscv64_virt_defconfig
make -j$(nproc)

# 生成的文件在 output/images/ 下:
#   fw_jump.elf   -> OpenSBI
#   Image          -> Linux 内核
#   rootfs.ext2    -> 根文件系统

2.4.2 启动命令

/opt/qemu-riscv/bin/qemu-system-riscv64 \
  -machine virt \
  -cpu rv64 \
  -smp 2 \
  -m 2G \
  -kernel output/images/fw_jump.elf \
  -device loader,file=output/images/Image,addr=0x80200000 \
  -drive file=output/images/rootfs.ext2,format=raw,if=virtio \
  -append "root=/dev/vda rw console=ttyS0" \
  -nographic

解释一下关键点:

  • -kernel 加载的是 OpenSBI,不是 Linux 内核。OpenSBI 会跳转到 0x80200000 去执行内核。
  • -device loader 把内核镜像放到指定内存地址。
  • -append 传递内核启动参数,console=ttyS0 让内核把日志输出到串口。
  • -nographic 把串口重定向到当前终端,不需要图形界面。

2.4.3 启动效果

执行上面的命令后,你会看到 OpenSBI 的启动信息,然后是 Linux 内核的启动日志,最后出现登录提示符。默认用户名 root,无密码。

OpenSBI v1.3
  ____  _____ ____  _____
 / __ \|  _ \ ___|/ ____|
| |  | | |_) | |  | (___  
| |  | |  _ <| |   \___ \ 
| |__| | |_) | |___ ____) |
 \____/|____/\___|_____/ 

Platform Name          : riscv-virtio
Platform Features      : medeleg

...
[    0.000000] Linux version 6.6.0 ...
[    0.123456] Built 1 zonelists, mobility grouping on.
...
Welcome to Buildroot
buildroot login: root
# 
第一个虚拟机启动成功! 这意味着你的 QEMU RISC-V 环境已经完整可用了。后续章节的性能分析、调试、故障定位,都基于这个环境展开。

嗯,这里有个小细节:如果你发现启动后卡在 console=ttyS0 没输出,八成是内核编译时没打开串口驱动。检查内核配置里的 CONFIG_SERIAL_8250 和 CONFIG_SERIAL_OF_PLATFORM 是否开启。

我个人习惯把这条启动命令存成一个脚本,比如 start-vm.sh,每次调试直接运行,不用重复敲参数。你也可以把参数写进 qemu-system-riscv64 的配置文件里,用 -readconfig 加载,更整洁。