Home

Awesome

YatSenOS RISC-V 64

Todo

Quick Start

首先安装riscv交叉编译工具链,安装方法可见博客 https://blog.nelson-cheung.cn/2021/12/16/riscv-toolchain-install-guide/ 。在项目目录下执行如下命令。

make && make run

可以看到输出如下。

OS最后只剩下两个进程分时共享处理器。目前只启动了一份用户程序进行验证,代码如下。(别问为什么,问就是懒

#include "stdio.h"
#include "syscall.h"

int main()
{
    printf("你好,进程!\n");

    unsigned long pid = fork();

    if (pid == 0)
    {
        printf("子进程启动, pid: %ld\n", pid);
    }
    else if (pid > 0)
    {
        printf("父进程fork返回, pid: %ld\n", pid);
    }
    else
    {
        printf("发生错误\n");
    }

    while(true);
}

Introduction

相比较于第一版的YatSenOS,修改如下。

项目的结构如下。

├── driver          驱动代码
├── gdbinit.txt     gdb指令,包括设置断点,连接target remote等,用于debug
├── include         头文件
├── kernel          内核代码实现
├── lib             用户标准库
├── LICENSE        
├── Makefile  
├── README.md
└── user            用户程序代码

各个子目录的文件描述如下。

文件描述
driver/clint.cppCLINT驱动实现
driver/driver.cpp驱动的集中定义
driver/timer.cpp时钟驱动实现
driver/uart.cppUART驱动实现
include/address_pool.h资源池声明
include/bitmap.h位图声明
include/clint.hCLINT驱动声明
include/constant.h常量声明
include/driver.h驱动管理器声明
include/interrupt.h中断管理器声明
include/list.h链表声明
include/mem.h内存管理器声明
include/object.h内核数据对象声明
include/pcb.hPCB声明
include/process.h进程管理器声明
include/rv64.hRISC-V架构相关代码声明
include/stdarg.h可变参数声明
include/stdio.h用户标准库声明
include/syscall.h系统调用声明
include/syscall_manager.h系统调用管理器声明
include/timer.h时钟驱动声明
include/uart.hUART驱动声明
include/user_process.h用户程序声明
include/utils.h工具函数声明
kernel/address_pool.cpp资源池实现
kernel/asm_utils.s汇编函数实现
kernel/bitmap.cpp位图实现
kernel/init.cpp内核初始化代码
kernel/interrupt.cpp中断管理器实现
kernel/list.cpp链表实现
kernel/mem.cpp内存管理器实现
kernel/process.cpp进程管理器实现
kernel/start.s内核进入点
kernel/syscall_manager.cpp系统调用管理器实现
kernel/utils.cpp工具函数实现
lib/lib_utils.s用户标准库汇编代码
lib/stdio.cpp用户标准库实现
lib/syscall.cpp用户系统调用实现

64位的进程地址空间排列如下。

和Linux定义的Virual Memory Layout不同,这里的Kernel被放在低地址,user被放在高地址。这样做的原因是qemuvirt板最开始会加载-kernel指定的ELF文件到0x80000000。在没有实现文件系统的情况下,必须将内核代码的起始地址设置为0x80000000,然后才可以让qemu正确加载内核。

还有一个原因是DRAM从0x80000000开始,建立了虚拟地址和物理地址的一一映射后,在内核中访问虚拟地址和物理地址是等同的。这可以为后面代码的编写带来方便。

对于qemu的virt板来说,其并没有完全实现RISC-V定义的中断转发机制。这就导致了machine mode的interrupt并不能被转发到supervisor mode中处理。而Kernel是运行在supervisor mode中的,也就是说,Kernel没有办法处理machine mode的interrupt。此时,可以通过设置mip来将中断转发到supervisor mode。但是,这会导致中断嵌套时保护现场和恢复现场存在问题。设想一个场景,user mode发送了一个ecall到supervisor中,而此时supervisor准备保存现场。但是,machine timer interrupt发生了,然后被转发到supervisor中。此时,supervisor timer interrupt会覆盖原来ecall设置的sepcscausesstatus,还有其他的寄存器。也就是说,在没有其他机制辅助的情况下,原来的ecall会被覆盖而无法被处理。此时,需要设计一个在machine、supervisor、user三者之间协同的中断转发机制来处理中断嵌套的问题。解决方法可以阅读源码,这里暂时按下不表。

详细的设计细节留到后面的文档再统一分析。

References

  1. In Praise of The RISC-V Reader [PDF]
  2. Volume 1, Unprivileged Spec v. 20191213 [PDF]
  3. Volume 2, Privileged Spec v. 20211203 [PDF]
  4. SiFive FU540-C000 Manual v1p0 [PDF]
  5. UART 16550 [PDF]
  6. Xv6 [LINK]
  7. rcore [LINK]
  8. https://groups.google.com/a/groups.riscv.org/g/sw-dev/c/1gpj3Z9Aqew
  9. https://twilco.github.io/riscv-from-scratch/2019/07/08/riscv-from-scratch-3.html
  10. https://github.com/twilco/riscv-from-scratch
  11. https://stackoverflow.com/questions/69133848/risc-v-illegal-instruction-exception-when-switching-to-supervisor-mode