1. 课程导入
各位同学,欢迎来到《QEMU RISC-V 设备树配置与Linux内核启动实战》的第一章。我是你们这门课的主讲工程师。说实话,做了十几年嵌入式底层开发,从ARM转到RISC-V,最大的感受就是——这个架构真的很「干净」。没有历史包袱,指令集手册薄薄一本,读起来很舒服。
这一章我们不急着敲命令。先把几个核心概念理清楚,再把开发环境搭好。磨刀不误砍柴工,你想想看,后面十几章我们都要在QEMU上折腾,如果环境没配好,那真是寸步难行。
1.1 RISC-V 架构简介
RISC-V 是一个基于精简指令集计算(RISC)原则的开源指令集架构(ISA)。它诞生于2010年,由加州大学伯克利分校的团队开发。我最早接触RISC-V是在2018年,当时一个客户想评估在RISC-V核上跑Linux的可能性。说实话,那时候工具链还不成熟,踩了不少坑。
RISC-V 有几个关键特点:
- 模块化设计:基础指令集(RV32I/RV64I)加上可选的扩展模块(M、A、F、D、C等)。你需要什么就加什么,不像ARM那样动辄几百页的架构手册。
- 完全开源:没有授权费,没有专利壁垒。这对创业公司来说太友好了。
- 简洁优雅:基础整数指令只有40多条。我记得第一次看完RV32I的指令集手册,只花了半天时间。
核心知识点:RISC-V 的「特权架构」定义了三种运行模式——机器模式(M-mode)、监督模式(S-mode)和用户模式(U-mode)。我们后面用QEMU启动Linux内核,内核就运行在S-mode下。这个分层设计,说白了就是为了安全和隔离。
RISC-V 的寄存器设计也很规整。32个通用寄存器 x0~x31,其中 x0 硬编码为0。这个设计我在做底层调试时觉得特别方便——有些ARM处理器要专门用MOV指令清0,RISC-V直接读 x0 就行。
| 寄存器 | ABI名称 | 用途说明 |
|---|---|---|
| x0 | zero | 硬编码为0,只读 |
| x1 | ra | 返回地址寄存器 |
| x2 | sp | 栈指针 |
| x5~x7 | t0~t2 | 临时寄存器 |
| x10~x17 | a0~a7 | 函数参数/返回值 |
1.2 QEMU 模拟器概述
QEMU 是一个开源的机器模拟器和虚拟化器。它能模拟从ARM到x86再到RISC-V的各种硬件平台。我个人习惯用QEMU做嵌入式开发的原型验证——不用等FPGA综合,不用买开发板,在笔记本上就能跑Linux内核。
QEMU 有两种工作模式:
- 全系统模拟(System Mode):模拟完整的硬件平台,包括CPU、内存、外设。我们这门课主要用这种模式。
- 用户模式模拟(User Mode):只模拟CPU,直接运行单个程序。适合交叉编译测试。
为什么选择QEMU做RISC-V开发?我总结了几点:
- 硬件无关性:你不需要买任何RISC-V开发板。QEMU模拟的virt平台足够跑Linux。
- 调试能力:QEMU内置GDB stub,可以单步调试内核。这个功能我在调试设备树解析代码时帮了大忙。
- 可重复性:每次启动都是相同的硬件配置,不会像真实硬件那样有信号抖动问题。
我的经验:QEMU模拟的virt平台虽然功能完整,但外设模型和真实硬件有差异。如果你最终要部署到FPGA或流片芯片上,建议在QEMU上验证逻辑正确性后,再用真实硬件做最终测试。
1.3 设备树(Device Tree)概念
设备树,说白了就是描述硬件信息的「数据文件」。它用树形结构告诉内核:CPU有几个核、内存多大、外设在哪个地址、中断号是多少。
为什么需要设备树?我举个例子。早期ARM Linux内核里,每个板子都要写一个 board file,里面硬编码了所有硬件信息。后来板子越来越多,内核代码变得臃肿不堪。设备树就是为了解决这个问题——把硬件描述从内核代码中剥离出来。
设备树的核心文件类型:
- .dts:设备树源文件,人类可读的文本格式。
- .dtsi:设备树包含文件,类似C语言的头文件。
- .dtb:设备树二进制文件,由dtc编译器从.dts生成。
一个简单的RISC-V设备树片段长这样:
/dts-v1/;
/ {
compatible = "riscv-virtio";
#address-cells = <2>;
#size-cells = <2>;
memory@80000000 {
device_type = "memory";
reg = <0x0 0x80000000 0x0 0x40000000>;
};
cpu@0 {
compatible = "riscv";
reg = <0>;
riscv,isa = "rv64imafdc";
};
};
避坑指南:我曾经在调试一个RISC-V板子时,发现内核启动后只识别了512MB内存,但硬件明明有1GB。查了两天才发现是设备树里 #address-cells 和 #size-cells 写错了。这两个字段决定了reg属性的解析方式,写错了内存地址就截断了。
1.4 开发环境准备
好了,概念讲完了,我们来动手搭环境。这部分我会给出具体的命令和版本建议。
1.4.1 安装 QEMU
我建议从源码编译QEMU,这样能确保RISC-V支持是最新的。当然,你也可以用包管理器安装,但版本可能比较旧。
# 下载QEMU源码
wget https://download.qemu.org/qemu-8.2.0.tar.xz
tar xvf qemu-8.2.0.tar.xz
cd qemu-8.2.0
# 配置,只编译RISC-V目标
./configure --target-list=riscv64-softmmu --enable-debug
# 编译安装
make -j$(nproc)
sudo make install
验证安装:
qemu-system-riscv64 --version
1.4.2 安装交叉编译工具链
RISC-V的交叉编译工具链,我推荐用官方的riscv-gnu-toolchain。如果你不想从源码编译,也可以直接用发行版提供的预编译包。
# Ubuntu/Debian
sudo apt-get install gcc-riscv64-linux-gnu
# 或者从源码编译
git clone https://github.com/riscv-collab/riscv-gnu-toolchain
cd riscv-gnu-toolchain
./configure --prefix=/opt/riscv --with-arch=rv64gc
make linux -j$(nproc)
我的建议:第一次用的话,直接用apt安装预编译包。等后面需要定制工具链时,再考虑从源码编译。我刚开始也是从源码编译,结果等了两个小时,后来发现apt装的就够用。
1.4.3 获取Linux内核源码
我们使用主线Linux内核,版本建议选6.x以上,对RISC-V支持比较完善。
git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux
git checkout v6.6
配置内核,启用RISC-V支持:
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- defconfig
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- -j$(nproc)
1.4.4 环境验证
环境搭好后,我们来验证一下。用QEMU启动一个最小的RISC-V Linux内核:
qemu-system-riscv64 \
-machine virt \
-nographic \
-kernel arch/riscv/boot/Image \
-append "console=ttyS0 root=/dev/vda" \
-drive file=rootfs.img,format=raw,id=hd0 \
-device virtio-blk-device,drive=hd0
如果看到内核启动日志,最后出现登录提示符,说明环境搭建成功。
环境清单:
- QEMU 8.2.0(或更高版本)
- riscv64-linux-gnu-gcc 12.x(或更高版本)
- Linux内核 6.6(或更高版本)
- 设备树编译器 dtc 1.6+
1.5 本章知识体系
下面这张图展示了本章的核心知识结构,以及它们之间的关系:
这张图把本章四个核心模块的关系理清楚了。RISC-V架构是基础,QEMU提供模拟运行环境,设备树负责描述硬件,环境搭建则是把工具链和内核准备好。四者缺一不可。