在RISC-V上移植系统

发布时间 2023-09-22 20:17:42作者: 日月久照

预备知识

GNU Binutils参考文档

GNU Binutils是一系列用来生成可执行文件的软件的集合体,它包括我们常常使用的ld,as等软件。即使你没有亲自使用过这些软件,只要你使用了gcc就相当于间接使用了它们,因为gcc会在生成可执行程序时使用这些软件。

我们需要准备好as和ld的参考文档,因为我们不可避免地要了解汇编和链接脚本。我不在博客中详细介绍他们的内容,因为没有比官方文档更好的教材了。

文档可以在GNU Binutils主页获取。

RISC-V参考文档

准备好如下文档,在不理解的时候可以参考

  1. 《The RISC-V Instruction Set Manual Volume I: Unprivileged ISA》
  2. 《The RISC-V Instruction Set Manual Volume II: Privileged Architecture》
  3. OpenSBI主页
  4. Qemu文档以及源代码

这里我们建议自己编译qemu,因为有的地方想要真正理解需要阅读qemu源代码。

我始终坚持一个观点,官方文档是最好的教材,所以我会尽可能指出每个知识点究竟在官方文档的那一部分。如果查阅官方文档无法给你答案,那么最好的办法就是咨询那些对这方面有了解的人。

RISC-V异常处理

根据RISC-V标准文档《The RISC-V Instruction Set Manual Volume I: Unprivileged ISA》,RISV异常处理的具体过程是由EEI(RISC-V execution environment interface)决定的。

首先我们必须明确一个观点,RISCV的特权等级分为三个层次,分别是M模式,S模式,U模式。SBI运行在M模式,操作系统内核运行在S模式,应用程序运行在U模式,这部分的资料可以在RISC-V特权文档中的第一章看到。

系统调用

这里我们主要说说系统调用。在U模式下运行的用户程序,一旦调用ecall指令,就会陷入到S模式中,操控权也就转交到内核手里。RISC-V的特权寄存器中有一个寄存器叫做stvec,这个寄存器储存了陷阱处理程序的基地址,具体内容可以参照RISC-V特权文档的4.1.2节。总之,在调用了ecall后,芯片将会跳转到stvec所指向的程序代码,因此我们需要在操作系统启动时设置好该寄存器的值。

RISC-V的启动过程

我们使用qemu模拟RISC-V来运行操作系统。我们从qemu命令行开始讲解一个内核被加载的过程:

qemu-system-riscv32 -kernel target -machine virt -m 64M -nographic -no-reboot \
  -global virtio-mmio.force-legacy=false \
  -drive file=target/fs.img,if=none,format=raw,id=hd \
  -device virtio-blk-device,drive=hd,bus=virtio-mmio-bus.0

一些参数的说明

  • -kernel kernel加载内核

  • -machine virt指明机器类型

  • -m 64M设置内存

  • -nographic关闭图形化显示

在机器启动时,首先会运行SBI加载内核,这里SBI起到了类似于Bootloader的作用,具体SBI的运行过程,我查找了很多资料都没有找到描述,最终还得依靠阅读qemu的源代码。

由于上面的命令行没有指定sbi文件,所以会使用默认的sbi文件,对于riscv32来说,这个文件名被定义在include/hw/riscv/boot.h中,为

#define RISCV32_BIOS_BIN    "opensbi-riscv32-generic-fw_dynamic.bin"

加载opensbi的函数为riscv_find_and_load_firmware,阅读代码会发现这个函数将opensbi加载到0x80000000这个位置上。

接下来opensbi会加载内核,阅读opensbi有关firmware的文档,我们发现opensbi有三种内核加载方式,分别是fw_dynamicfw_payloadfw_jump,而我们默认的固件使用的是fw_dynamic加载方式。这种方式会将内核加载到qemu给出的地址上。

qemu负责加载内核的函数是riscv_load_kernel,在这个函数中我们发现,如果内核是elf文件(这也是我们这次试验选择的方式),那么内核加载地址将根据elf的程序入口决定。但是注意不要与固件加载的地方发生冲突,也不要和virt的内存布局不协调。这里我建议在riscv32上将内存布局加载到0x80400000上,因为这是qemu在没有elf文件配置的情况下自行计算出的程序入口,最符合qemu的习惯。