引入:计算机没有黑魔法
例如我们都可以,通过指令来查看我们计算机的信息
“Everything is a State Machine”
- 在许多状态之间不断切换程序就运行了起来
- Makefile 也是程序;它也是状态机
- 程序不好读的话,我们还可以调试它!
-
- 计算机系统公理:你想到的就一定有人做到
- “总有人会去发明轮子的”
- 然后 相信编译器一定是对的
risc-v-%E5%9B%BA%E4%BB%B6%E4%B8%8E%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%BC%95%E5%AF%BC">RISC-V: 固件与操作系统引导
灵魂三问
- RISC-V 系统是如何复位、执行什么固件、如何加载操作系统的?
- OpenSBI 的入口位于什么地方?
- _start 开始的 _try_lottery 是做什么的?
一、RISC-V 系统复位与启动流程详解
1. 复位机制与固件执行
RISC-V 系统的复位流程由硬件信号触发,所有核心跳转至复位向量地址(如 FU540 芯片的 0x1004),进入 M-mode 执行固化在 ROM 中的 ZSBL(第零阶段引导程序)。
ZSBL 根据 MSEL 引脚配置加载下一阶段固件(如 FSBL),FSBL 负责初始化 DDR 内存、时钟和中断控制器,随后将控制权移交至 OpenSBI
关键步骤:
- ZSBL:从 ROM 中加载 FSBL 到 L2 LIM 缓存(地址 0x0800_0000)。
- FSBL:配置 DDR 并加载 BBL 或 OpenSBI 到 DDR(地址 0x8000_0000)。
- OpenSBI:作为 M-mode 运行时服务,通过 SBI 调用为操作系统提供硬件抽象。
2. 操作系统加载
OpenSBI 完成硬件初始化后,将跳转至操作系统的入口地址(如 Linux 内核的 _start
),通过设备树(DTB)传递硬件参数。U-Boot 可作为中间加载器,支持从 Flash、网络等介质加载内核镜像。
二、OpenSBI 入口与调试方法
1. 入口地址
OpenSBI 的入口通常位于 0x8000_0000(DDR 起始地址),这是 RISC-V 虚拟化平台(如 QEMU virt)的标准配置。可通过 QEMU 启动参数验证:
qemu-system-riscv64 -machine virt -bios opensbi.bin
启动后 OpenSBI 会输出固件基地址和大小。
2. 调试建议
- GDB 跟踪:在 QEMU 中启用 GDB 调试,断点设置在
_start
或 OpenSBI 初始化函数。 - SBI 调用监控:通过修改 OpenSBI 源码添加日志,观察
ecall
指令的参数传递(如 EID 和 FID)。
三、_start
与 _try_lottery
分析
1. 函数作用
_try_lottery
常见于多核启动场景,用于 动态选择主核(Boot HART)。其逻辑通常包括:
- 检查当前 HART ID 是否为主核。
- 若非主核,则进入等待循环;主核继续执行后续初始化(如加载设备树、启动调度器)。
2. 优化建议
- 锁机制:使用原子操作或硬件锁避免多核竞争。
- 性能分析:通过 PMU(性能监控单元)统计各核启动延迟,优化任务分配策略。
四、建议
1. 调试与验证工具
- QEMU + GDB:模拟 RISC-V 环境,单步跟踪启动流程。
- RISC-V Trace:使用 SiFive Trace 或 Lauterbach 硬件跟踪器,捕获复位到操作系统的完整指令流。
2. 固件定制
- OpenSBI 扩展:基于 SBI 规范开发自定义扩展(如安全启动),通过
ecall
提供专属服务。 - U-Boot 集成:在 S-mode 运行 U-Boot,利用其文件系统和网络协议栈加载复杂内核。
3. 性能优化
- DDR 初始化加速:预计算 PLL 配置参数,减少 FSBL 阶段的延时。
- 并行启动:在多核系统中,让从核提前初始化外设,缩短整体启动时间。
4. 安全增强
- IOPMP 配置:通过内存保护单元限制外设访问权限,防止恶意代码篡改引导链(参考 SpacemiT V100 设计,[1])。
- 可信执行环境(TEE):集成 OP-TEE,在 OpenSBI 阶段验证内核签名。
五、总结与扩展方向
问题 | 核心要点 | 扩展方向 |
RISC-V 复位与启动 | 多阶段引导(ZSBL→FSBL→OpenSBI) + SBI 抽象层 | 安全启动、多核协同 |
OpenSBI 入口 | 0x8000_0000(DDR 基址) + SBI 服务网关 | 自定义 SBI 扩展、性能监控 |
| 主核选择逻辑 + 多核同步机制 | 锁优化、PMU 分析 |
推荐实践:
- 使用 QEMU 模拟不同 MSEL 配置,观察启动路径变化([3])。
- 修改 OpenSBI 源码,添加自定义 SBI 调用(如动态频率调节)。
- 结合 Lauterbach Trace32 分析
_try_lottery
的执行时序,优化多核启动效率。
OpenSBI
OpenSBI: 一个开源的 RISC-V 引导固件,用于管理 RISC-V 平台的启动过程。
- 它实现了 SBI(Supervisor Binary Interface)规范,为操作系统提供了硬件抽象层,简化了系统启动和硬件管理。
- OpenSBI 支持多种 RISC-V 处理器和平台,广泛用于嵌入式系统和开发板。
一、环境准备与依赖安装
1. 安装编译工具链
# 安装基础工具
sudo apt-get install build-essential git device-tree-compiler
# 安装 RISC-V 交叉编译器(以 riscv64-unknown-elf 为例)
sudo apt-get install gcc-riscv64-unknown-elf
确保交叉编译器路径已添加到环境变量(若未自动添加):
export PATH=$PATH:/usr/local/riscv64-unknown-elf/bin
2. 获取 OpenSBI 源码
git clone https://github.com/riscv-software-src/opensbi.git
cd opensbi
# 切换至稳定版本(例如 v1.2)
git checkout v1.2
二、编译流程详解
1. 核心编译命令解析
OpenSBI 的编译通过 Makefile
控制,关键参数如下:
make CROSS_COMPILE=riscv64-unknown-elf- PLATFORM=generic
CROSS_COMPILE
:指定交叉编译工具链前缀(如riscv64-unknown-elf-
)PLATFORM
:目标平台(如generic
或qemu/virt
,具体平台代码位于platform/
目录)FW_JUMP_ADDR
:可选参数,指定跳转地址(如0x80200000
用于 Linux 内核加载)
2. 分步编译操作
# 创建输出目录
mkdir -p output/opensbi
# 执行编译(以 QEMU virt 平台为例)
make CROSS_COMPILE=riscv64-unknown-elf- PLATFORM=qemu/virt
# 复制生成文件至输出目录
cp build/platform/qemu/virt/firmware/fw_jump.bin output/opensbi/
生成文件说明:
fw_jump.bin
:跳转型固件,需手动指定下一阶段地址fw_payload.bin
:包含嵌入式内核的固件(如结合 U-Boot)
三、Makefile 关键逻辑解析
1. 平台配置加载
Makefile 会加载 platform/<PLATFORM>/objects.mk
,定义平台相关源文件和链接脚本([1])。例如:
# platform/qemu/virt/objects.mk
platform-objs-y += platform/qemu/virt/sbi_platform.o
firmware-objs-$(CONFIG_FW_JUMP) += firmware/fw_jump.o
2. 固件生成流程
- 汇编启动代码:
firmware/fw_base.S
定义入口_start
,处理多核同步和重定位([1])。 - C 代码初始化:
lib/sbi/sbi_init.c
初始化设备树、异常处理、定时器等([1])。 - 链接脚本控制:
firmware/fw_base.lds
指定代码段布局(.text
起始地址为FW_TEXT_START
,默认0x80000000
)。
四、调试与验证
1. QEMU 模拟运行
qemu-system-riscv64 -machine virt -bios output/opensbi/fw_jump.bin -nographic
输出示例:
OpenSBI v1.2
Platform Name : QEMU Virt Machine
Firmware Base : 0x80000000
Firmware Size : 112 KB
Runtime SBI Version : 1.0
2. GDB 调试技巧
# 启动 QEMU 调试模式
qemu-system-riscv64 -machine virt -bios fw_jump.bin -s -S -nographic
# 另开终端连接 GDB
riscv64-unknown-elf-gdb build/platform/qemu/virt/firmware/fw_jump.elf
(gdb) target remote :1234
(gdb) b _start # 在入口点设置断点
(gdb) c # 继续执行
观察点:
- 检查
a0
(HART ID)、a1
(设备树地址)寄存器初始化值 - 跟踪
sbi_init
函数调用链([1])
五、常见问题与解决方案
1. 编译错误:未找到交叉编译器
- 原因:
CROSS_COMPILE
路径未正确设置 - 修复:确认编译器可执行文件存在,例如:
ls /usr/local/riscv64-unknown-elf/bin/riscv64-unknown-elf-gcc
2. 启动时卡在 _wait_for_boot_hart
- 分析:非主核(Boot HART)未正确释放锁
- 调试:修改
fw_base.S
添加调试输出(需重新编译):
/* 在 _wait_for_boot_hart 循环前添加 */
la a0, msg_wait
call sbi_platform_console_puts
3. 自定义平台适配
- 复制现有平台模板(如
platform/generic/
)至新目录 - 修改
objects.mk
添加平台特定驱动 - 编译时指定
PLATFORM=new_platform
对于分析 Makefile
使用 make -nB > a.log
查看实际执行的命令,
- grep 过滤干扰项;sed 让输出更易读
- 然后再 vim 查看可使‘ ' --> /n
- 逐级分析 Makefile 中的目标依赖关系。
我们也可以借助之前实现的 ag api:通过 output-->program 的想法, 借助 vim -->ai 来让 ai 帮我们实现注释
直接让AI帮你写注释 调格式,读五百多行的makefile文件也不是梦了🙂