3. QEMU入门

好,咱们进入第三章。这一章我打算把 QEMU 支持的 RISC-V 机器类型掰开揉碎讲清楚。你可能会问,不就是个模拟器吗,选哪个机器类型差别很大?

嗯,差别还真不小。我刚开始接触 RISC-V 虚拟化时,就因为在 virt 和 sifive_u 之间选错了平台,折腾了一整天设备树。所以这一章,咱们把底层的硬件布局和启动流程彻底搞明白。

3.1 QEMU 支持的 RISC-V 机器类型概览

QEMU 目前为 RISC-V 提供了几种主要的机器类型。我个人最常用的是 virt,但其他平台也有各自的用武之地。

机器类型 描述 适用场景
virt 通用虚拟平台,无真实硬件对应 开发、测试、OpenSBI 移植、Linux 内核调试
sifive_u 模拟 SiFive Unleashed 开发板 嵌入式开发、RISC-V 板级验证
spike 基于 Spike ISA 模拟器的平台 ISA 级别测试、裸机程序开发
microchip_pfsoc 模拟 Microchip PolarFire SoC FPGA 开发、工业级应用验证

核心建议: 除非你有特定的板级需求,否则 virt 平台永远是 RISC-V 虚拟化的首选。它灵活、可配置,而且设备树生成规则最清晰。

3.2 virt 平台深度解析

virt 平台是 QEMU 专门为虚拟化设计的。它没有绑定任何真实硬件,所以你可以自由配置内存大小、CPU 数量、外设种类。说白了,它就是一张白纸,你想画什么就画什么。

3.2.1 内存布局

virt 平台的内存布局是固定的。我整理了一张表,你对照着看会很清楚:

起始地址 大小 描述
0x00000000 — 保留区域(通常为 0)
0x80000000 可变 主内存(DRAM)起始地址
0x10000000 0x1000 CLINT(核心本地中断控制器)
0x10001000 0x1000 PLIC(平台级中断控制器)
0x10000000 0x1000 UART0(串口)
0x10001000 0x1000 RTC(实时时钟)
0x10002000 0x1000 Flash 控制器

避坑指南: 我曾经在配置 virt 平台时,把主内存起始地址改成了 0x80200000,结果 OpenSBI 死活起不来。后来才发现,virt 平台的 DRAM 必须从 0x80000000 开始,这是 QEMU 硬编码的。你改不了,也别想着改。

3.2.2 外设详解

virt 平台的外设都是虚拟的,但行为上模拟了真实硬件。咱们挑几个关键的说说:

  • CLINT(核心本地中断控制器):负责处理定时器中断和软件中断。每个 CPU 核都有独立的 CLINT 寄存器组。
  • PLIC(平台级中断控制器):管理所有外部设备的中断。比如 UART 收到数据时,会通过 PLIC 通知 CPU。
  • UART0(串口):基于 NS16550A 标准。这是你调试时最重要的输出通道。我习惯在 OpenSBI 里把串口波特率设为 115200,稳定可靠。
  • RTC(实时时钟):提供系统时间。OpenSBI 启动时会用它来初始化计时器。

3.3 sifive_u 平台解析

sifive_u 平台模拟的是 SiFive Unleashed 开发板。它比 virt 平台更接近真实硬件,但灵活性差一些。

特性 virt 平台 sifive_u 平台
硬件对应 无 SiFive FU540 SoC
CPU 数量 可配置(1-8) 固定 5 核(4+1)
内存布局 灵活 固定
设备树 自动生成 固定模板
适用场景 通用开发 板级验证

注意: sifive_u 平台的设备树是 QEMU 内部写死的。如果你想修改设备树,必须用 -dtb 参数覆盖。而 virt 平台的设备树是动态生成的,你可以通过 -machine 参数灵活控制。

3.4 启动流程概览

RISC-V 的启动流程,说白了就是一条链:ROM → OpenSBI → U-Boot → Linux Kernel。但 QEMU 里有些特殊之处。

3.4.1 标准启动流程

  1. QEMU 初始化:加载固件(如 OpenSBI)到内存指定位置。
  2. CPU 复位:所有 CPU 核从 0x80000000 开始执行。
  3. OpenSBI 启动:初始化硬件、设置中断、加载设备树。
  4. 跳转到下一级:OpenSBI 根据设备树中的 chosen 节点,跳转到内核入口。
  5. 内核启动:解压、初始化、挂载根文件系统。

3.4.2 QEMU 特有的启动细节

QEMU 里有个特殊机制:直接启动内核。你可以用 -kernel 参数直接加载 Linux 内核,QEMU 会自动帮你跳过 OpenSBI 的引导过程。但我不建议这么做,因为你会失去 OpenSBI 提供的很多调试信息。

我的经验: 在开发阶段,我永远用 -bios 参数指定 OpenSBI。这样启动时能看到 OpenSBI 的打印信息,一旦出问题,能快速定位是 OpenSBI 的问题还是内核的问题。

3.5 核心知识结构图

下面这张图展示了 QEMU RISC-V 平台的核心知识结构。你可以看到,机器类型、内存布局、外设和启动流程是相互关联的。

QEMU RISC-V 平台核心知识结构 QEMU RISC-V 平台核心知识结构 QEMU RISC-V 平台 机器类型 内存与外设 启动流程 virt sifive_u spike microchip DRAM 布局 CLINT/PLIC UART/RTC 设备树 QEMU 初始化 OpenSBI U-Boot Linux 内核 核心要点 virt 平台最灵活,适合开发调试 设备树是连接硬件与软件的桥梁

3.6 实战:启动一个最小系统

光说不练假把式。咱们用 virt 平台启动一个最小系统看看效果。

# 启动 QEMU virt 平台,加载 OpenSBI
qemu-system-riscv64 \
    -machine virt \
    -m 256M \
    -smp 2 \
    -bios /path/to/opensbi/build/platform/generic/firmware/fw_jump.bin \
    -kernel /path/to/linux/arch/riscv/boot/Image \
    -append "console=ttyS0 root=/dev/vda" \
    -drive file=/path/to/rootfs.img,format=raw,id=hd0 \
    -device virtio-blk-device,drive=hd0 \
    -nographic

小技巧: 启动时加上 -d int 参数,QEMU 会打印中断信息。我在调试 PLIC 配置时,这个参数帮了大忙。

启动后你会看到 OpenSBI 的打印信息,然后是 Linux 内核的启动日志。如果一切顺利,你会得到一个可以交互的 shell。

嗯,这一章的内容就到这里。你掌握了 QEMU RISC-V 平台的机器类型、内存布局和启动流程,接下来就可以开始配置设备树了。