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 准备启动文件
你需要三个文件:
- 内核镜像:
Image(编译Linux内核得到) - 设备树文件:
virt.dtb(QEMU可以自动生成,也可以手动导出) - 根文件系统:
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的完整数据流和组件关系。你可以把它当作本章的思维导图。
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。别急,一步一步来。