3. RISC-V设备树规范

好,咱们进入第三章。这一章是真正动手的开始。

前两章我们把RISC-V指令集和QEMU的编译环境都搭好了。现在,是时候让QEMU真正跑起来,载入一个完整的RISC-V Linux系统。

说实话,我第一次看到QEMU那密密麻麻的命令行参数时,头都大了。但后来我发现,只要抓住几个核心参数,剩下的都是套路。今天我就带你把这些套路捋清楚。

3.1 QEMU支持的RISC-V开发板

QEMU为RISC-V提供了多种模拟开发板。你不需要买硬件,就能体验从裸机到Linux的完整开发流程。

我个人最常用的两块板子是 virt 和 sifive_u。它们代表了两种典型场景。

开发板型号 机器名 核心特点 适用场景
virt virt 纯虚拟板卡,无硬件限制,内存/外设可任意配置 Linux内核开发、驱动原型验证、CI自动化测试
Sifive U系列 sifive_u 模拟SiFive FU540 SoC,包含DDR、串口、SD卡控制器 接近真实硬件的固件开发、OpenSBI/U-Boot移植
Spike spike RISC-V官方ISA模拟器,非QEMU原生,但可配合使用 指令集验证、微架构研究
我的建议: 初学者先从 virt 板入手。它最灵活,出问题也最容易排查。等你在virt上跑通了Linux,再切换到 sifive_u 去折腾OpenSBI和U-Boot,会顺手很多。

3.2 理解QEMU命令行参数

QEMU的命令行参数虽然多,但核心就几类。我习惯把它们分成三组:机器配置、CPU与内存、外设与启动。

咱们先看一个最精简的启动命令,然后逐段拆解:

qemu-system-riscv64 \
    -machine virt \
    -cpu rv64 \
    -m 2G \
    -smp 4 \
    -kernel Image \
    -append "root=/dev/vda ro console=ttyS0" \
    -drive file=rootfs.ext4,format=raw,id=hd0 \
    -device virtio-blk-device,drive=hd0 \
    -nographic

这个命令看着长,其实每一段都有明确分工。

3.2.1 机器与CPU参数

  • -machine virt:指定模拟的开发板为virt。你也可以换成 sifive_u。
  • -cpu rv64:指定CPU为64位RISC-V。QEMU支持 rv64、rv32,甚至带向量扩展的 rv64,v=true。
  • -m 2G:分配2GB内存。virt板支持动态调整,sifive_u则受硬件限制。
  • -smp 4:模拟4个CPU核心。我在调试多核调度问题时,经常把这个值改成8来测试。

3.2.2 内核与启动参数

  • -kernel Image:指定内核镜像文件。这里的 Image 是编译Linux内核后生成的未压缩镜像。
  • -append "...":向内核传递启动参数。比如 root=/dev/vda 告诉内核根文件系统在哪个设备上,console=ttyS0 指定串口作为控制台。
我曾经踩过的坑: 第一次启动时,我忘了加 console=ttyS0,结果内核启动了,但终端上什么输出都没有。我还以为是QEMU坏了,折腾了半天。记住,QEMU默认的串口就是 ttyS0,不加这个参数,你就看不到内核的启动日志。

3.2.3 块设备与存储

  • -drive file=rootfs.ext4,format=raw,id=hd0:创建一个虚拟硬盘,映射到本地的 rootfs.ext4 文件。
  • -device virtio-blk-device,drive=hd0:将上面创建的虚拟硬盘挂载为virtio块设备。virtio是QEMU推荐的半虚拟化I/O方案,性能远优于模拟的IDE或SATA。

3.2.4 显示与交互

  • -nographic:不使用图形窗口,所有输入输出通过当前终端进行。这是服务器开发和自动化测试的标配。

如果你需要图形界面,可以去掉 -nographic,加上 -vga virtio 或 -display gtk。但说实话,做嵌入式开发,99%的时间我都在用 -nographic。

3.3 第一次启动RISC-V Linux

好,理论说完了,咱们来点实际的。下面是我整理的一个完整启动流程,你跟着做一遍,就能看到RISC-V Linux的启动日志了。

3.3.1 准备启动文件

你需要三个文件:

  1. 内核镜像:Image(编译Linux内核得到)
  2. 设备树文件:virt.dtb(QEMU可以自动生成,也可以手动导出)
  3. 根文件系统:rootfs.ext4(可以用Buildroot或Debian的riscv64镜像)

如果你手头没有这些文件,别急。QEMU自带的 -bios 参数可以配合OpenSBI启动。但那是后面章节的内容。今天咱们先走标准Linux启动路径。

3.3.2 启动命令(完整版)

qemu-system-riscv64 \
    -machine virt \
    -cpu rv64 \
    -m 2G \
    -smp 2 \
    -kernel Image \
    -append "root=/dev/vda rw console=ttyS0 earlycon=sbi" \
    -drive file=rootfs.ext4,format=raw,id=hd0 \
    -device virtio-blk-device,drive=hd0 \
    -netdev user,id=net0 \
    -device virtio-net-device,netdev=net0 \
    -nographic

注意,我加了一个 -netdev user 和 virtio-net-device。这样你的虚拟机就能通过QEMU的用户模式网络访问外网了。虽然性能一般,但用来 apt-get 装个包完全够用。

3.3.3 启动日志解读

执行上面的命令后,你会看到类似下面的输出:

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

Platform Name             : riscv-virtio,qemu
Platform Features         : medeleg
Boot HART                 : 0
...

[    0.000000] Linux version 6.1.0 ...
[    0.000000] OF: fdt: Machine model: riscv-virtio,qemu
[    0.000000] Memory: 2048MB available
[    0.000000] CPU: rv64imafdc
...
[    1.234567] VFS: Mounted root (ext4 filesystem) on device 254:0.
[    1.345678] Freeing unused kernel image (initmem) memory: 1024K
[    1.456789] Run /sbin/init as init process

Welcome to Buildroot
buildroot login: 

看到 Welcome to Buildroot 和 login: 提示符了吗?恭喜你,你的RISC-V Linux已经成功启动了!

关键点: 启动流程分为三个阶段:
1. OpenSBI(M模式):初始化硬件,提供SBI服务。
2. Linux内核(S模式):解压并初始化,挂载根文件系统。
3. 用户空间(U模式):运行init进程,启动shell。
这三个阶段对应了RISC-V的三种特权级,后面我们会详细讲。

3.4 知识结构图:QEMU系统仿真核心逻辑

下面这张图总结了QEMU启动RISC-V Linux的完整数据流和组件关系。你可以把它当作本章的思维导图。

QEMU RISC-V 系统仿真核心逻辑 用户命令行参数 QEMU 参数解析器 -machine -cpu -m -kernel -drive -device -nographic CPU & 内存 riscv64 CPU 2GB RAM 外设模型 virtio-blk / virtio-net UART (ttyS0) 固件加载 OpenSBI (M模式) Linux Kernel (S模式) Linux 启动日志 / Shell 终端 说明:用户参数 → QEMU解析 → 硬件模拟(CPU/内存/外设/固件)→ Linux启动 → 终端输出

3.5 常见问题与避坑指南

启动过程中,你可能会遇到一些问题。我把最常见的几个列出来,省得你走弯路。

问题现象 可能原因 解决方案
启动后黑屏,无任何输出 缺少 console=ttyS0 参数 在 -append 中加上 console=ttyS0
内核崩溃,提示 Kernel panic - not syncing: VFS: Unable to mount root fs 根文件系统设备名不对,或驱动未编译 检查 root= 参数,确保内核支持virtio驱动
QEMU报错 qemu-system-riscv64: Unable to load kernel 内核镜像路径错误,或镜像格式不对 确认 -kernel 指向的是未压缩的 Image,而不是 zImage 或 vmlinux
网络不通 未添加 -netdev 和 -device 网络参数 加上 -netdev user,id=net0 -device virtio-net-device,netdev=net0
我曾经踩过的坑: 有一次我编译内核时忘了开启 CONFIG_VIRTIO_BLK,结果QEMU怎么都挂不上根文件系统。内核日志显示 VFS: Cannot open root device "vda"。我查了半天才发现是内核配置的问题。所以,编译内核前,一定要确认 virtio 相关的驱动都选上了。

3.6 本章小结

这一章我们做了三件事:

  • 认识了QEMU支持的RISC-V开发板,特别是 virt 和 sifive_u 的区别。
  • 拆解了QEMU命令行参数,知道了 -machine、-cpu、-kernel、-drive 这些参数是干什么的。
  • 成功启动了第一个RISC-V Linux系统,看到了OpenSBI和内核的启动日志。

嗯,到这里,你已经迈出了RISC-V固件开发的第一步。后面的章节,我们会深入OpenSBI、U-Boot,甚至自己写一个最小的RTOS。别急,一步一步来。