xv6-in-openSBI
最近完成操作系统大赛,需要为 xv6 实现一个 SBI 来完成指定的引导
TODO:理一下 xv6 原本的引导过程
引导过程
OpenSBI
OpenSBI的主要功能就是在M态初始化硬件,然后以S态跳转到内核的位置,完成整个启动过程。此外,OpenSBI作为S态内核的执行环境(EEI),可以以 ecall
的方式为S态内核提供一些只有M态才能实现的功能
每个cpu启动时,都在M态。在将控制权交给操作系统时,转换为 S 态
qemu 自带一个 opensbi
指定 -bios default 这个选项
由于 opensbi 占据了 0x80000000 到 0x80200000 这段区域,因此我们需要把 xv6 原本这段区域(Kernel)迁移到 0x80200000
加入opensbi后的内存布局
1 |
|
为了加入 SBI,需要更改的地方
kernel.ld
里面的内核起始地址entry.s
里面,- 由于 sbi 把 hart id 放在了 a0 寄存器,xv6 是从 tp 寄存器读出 hart id 的,因此这里需要一个 mv 指令
- 然后在这里要设置好对应核的 stack,每个核开了 4096 byte,需要设置好 sp 寄存器
- 然后更改 xv6 的时钟中断实现
- 因为 xv6 的时钟中断是在 m 态实现的,要实现 sbi 我们一直都会在 S 态,所以利用sbi提供的接口在 s 态实现了时钟中断
- 更改
trap.c
中scause
为时钟中断时的动作
多核启动流程
由于 qemu 启动时的启动核不一定是 0,可能是随机的,目前采用的方式是
注意这种启动方式可能启动核不是 0,而是其他,后期可能需要更改
设置了一个共享变量
volatile static int boot_hart = -1;
启动核进入 if 判断,初始化自己,然后更改这个共享变量,利用 sbi 来唤醒其他核
初始化流程:
更改 boot hart
初始化 printf (采用uart)
打印启动信息
初始化物理内存分配
初始化 kernel page table
开启分页
初始化进程
初始化根文件系统 (在proc.c里面)
初始化 trap 并设置好 kernelvec
初始化中断代理 PLIC
初始化 buffer cache
初始化 File table
初始化 virtio 驱动
开启 init 进程
使用
SBI_HART_START
启动其他核初始化根文件系统
开启
init
进程
其他核进入else,初始化自己,调用那些
*inithart
的 函数初始化流程
- 开启分页
- 设置 kernelvec
- PLIC 中断代理开启
1 |
|
摘自FarmOs:
虽然说一个核就能完成大部分初始化工作,但因为架构要求,多核还是有一些需要各自初始化的代码。多核环境下需要注意这些事项:
- 每个核需要设置独立的页表。允许各个核在运行阶段使用不同的页表
- 每个核有独立的核内时钟,为每个核单独计时
- 每个核有独立的中断处理向量,我们一般将其设为同一个位置
- 每个核有独立的外部中断控制器
- 当一个核与其他核访问同一个变量或同一块内存时,为了缓存能够及时同步,需要加
fence
指令,保证之前的所有读写指令在fence之后都已同步到内存。对应gcc的语法为__sync_synchronize()
SBI和BIOS和BOOTLOADER的关系
摘自 rCore
SBI 是 RISC-V Supervisor Binary Interface 规范的缩写,OpenSBI 是RISC-V官方用C语言开发的SBI参考实现;RustSBI 是用Rust语言实现的SBI。
BIOS 是 Basic Input/Output System,作用是引导计算机系统的启动以及硬件测试,并向OS提供硬件抽象层。
机器上电之后,会从ROM中读取引导代码,引导整个计算机软硬件系统的启动。而整个启动过程是分为多个阶段的,现行通用的多阶段引导模型为:
ROM -> LOADER -> RUNTIME -> BOOTLOADER -> OS
- Loader 要干的事情,就是内存初始化,以及加载 Runtime 和 BootLoader 程序。而Loader自己也是一段程序,常见的Loader就包括 BIOS 和 UEFI,后者是前者的继任者。
- Runtime 固件程序是为了提供运行时服务(runtime services),它是对硬件最基础的抽象,对OS提供服务,当我们要在同一套硬件系统中运行不同的操作系统,或者做硬件级别的虚拟化时,就离不开Runtime服务的支持。SBI就是RISC-V架构的Runtime规范。
- BootLoader 要干的事情包括文件系统引导、网卡引导、操作系统启动配置项设置、操作系统加载等等。常见的 BootLoader 包括GRUB,U-Boot,LinuxBoot等。
而 BIOS/UEFI 的大多数实现,都是 Loader、Runtime、BootLoader 三合一的,所以不能粗暴的认为 SBI 跟 BIOS/UEFI 有直接的可比性。
如果把BIOS当做一个泛化的术语使用,而不是指某个具体实现的话,那么可以认为 SBI 是 BIOS 的组成部分之一。
也可参考这份文稿《An Introduction to RISC-V Boot Flow》的P5, P7, P9-11。
题外话:
计算机最重要的思想之一就是分层抽象,在任意两层之间,还可以按照设计者的意愿再次添加抽象层。而软件架构的设计和实现,是为了解决现实世界的具体问题,会面临资源、财力、物力、人力、时间等多种因素的掣肘,就会诞生一些“不那么规矩”、“不那么单纯”的架构或组件/软件,它们往往会跨层次,跨模块,大模块拆小,小模块合并,甚至打破一些“金科玉律”等等。
所以,相比于弄懂一个名词,更多的精力应该放在理解事物的本质上,只要把解决问题的流程和方法弄明白了,解决问题的过程中所用到的子流程、工具、方法,你爱怎么叫怎么叫,甚至自己发明名词也可以(只是与外人沟通可能会不太顺畅)。