uCore OS(on RISC-V64)——LAB0.5:最小可执行内核

实验目的

  1. 逐步掌握以下过程:

  2. 源码是如何被编译成可执行文件的。

  3. 编译成可执行文件后,计算机如何加载操作系统。

  4. 加载以后,该从哪里去运行操作系统。

  5. 操作系统的输出信息是怎么输出的呢。

实验内容

  1. 跟着实验指导书的步伐,阅读框架代码。
  2. 结合框架代码,深刻理解RISC-v。
  3. 内核的内存布局和入口点设置
  4. 通过sbi封装好输入输出函数
  5. 借助bootloader:OpenSBI初始化OS,完成练习。
  6. 按要求撰写实验报告。

内存布局和入口点设置

QEMU模拟器提供一个RISC-V的CPU和物理内存以及总线的通信功能。

此时,需要将硬盘上的操作系统内核加载到内存中,以便操作系统的执行。

完成这项工作的是bootloader,它负责开机(boot)以及将操作系统加载到内核(load)。在QEMU中提供了固件OpenSBI完成这项工作。

整个内核的入口点是kern/init/entry.S

#include 
#include 

    .section .text,"ax",%progbits
    .globl kern_entry
kern_entry:
    la sp, bootstacktop	//la,load address,即将返回地址保存在sp寄存器中
    
    tail kern_init		//tail是tail call的缩写是RISC-V的一条伪指令,相当于函数调用
    
.section .data
    # .align 2^12
    .align PGSHIFT
    .global bootstack
bootstack:
    .space KSTACKSIZE
    .global bootstacktop
bootstacktop:

在内核的入口点处调用了唯一的函数kern_init

int kern_init(void) __attribute__((noreturn));

int kern_init(void) {
    extern char edata[], end[];
    memset(edata, 0, end - edata);
    const char *message = "(THU.CST) os is loading ...\n";
    cprintf("%s\n\n", message);
    while (1);
}

这个函数打印了一个字符串"(THU.CST) os is loading …\n"之后就进入了循环阶段,打印这个字符串的函数是cprintf,之所以不选择直接使用printf函数输出,是因为C语言的标准库函数依赖glibc提供的运行时环境。

通过sbi封装好输入输出函数

首先通过系统调用指令ecall(environment call)来实现打印字符串。但是C语言不能直接写汇编代码需要在程序中加入__asm__ volatile关键字将实现内联汇编。

// libs/sbi.c

#include 
#include 
//SBI编号与函数对应
//编号0-8由OpenSBI处理,否则交给中断处理程序
uint64_t SBI_SET_TIMER = 0;
uint64_t SBI_CONSOLE_PUTCHAR = 1; 
uint64_t SBI_CONSOLE_GETCHAR = 2;
uint64_t SBI_CLEAR_IPI = 3;
uint64_t SBI_SEND_IPI = 4;
uint64_t SBI_REMOTE_FENCE_I = 5;
uint64_t SBI_REMOTE_SFENCE_VMA = 6;
uint64_t SBI_REMOTE_SFENCE_VMA_ASID = 7;
uint64_t SBI_SHUTDOWN = 8;
//核心函数
uint64_t sbi_call(uint64_t sbi_type, uint64_t arg0, uint64_t arg1, uint64_t arg2) {

    uint64_t ret_val;

    __asm__ volatile (
        "mv x17, %[sbi_type]\n"
        "mv x10, %[arg0]\n"
        "mv x11, %[arg1]\n"
        "mv x12, %[arg2]\n" //把参数的值放入寄存器
        "ecall\n"           //通过ecall交给OpenSBI执行
        "mv %[ret_val], x10"
        //OpenSBI按照riscv的calling convention,把返回值放到x10寄存器里
        : [ret_val] "=r" (ret_val)
        : [sbi_type] "r" (sbi_type), [arg0] "r" (arg0), [arg1] "r" (arg1), [arg2] "r" (arg2)
        : "memory"
    );
    return ret_val;
}

void sbi_console_putchar(unsigned char ch) {

    sbi_call(SBI_CONSOLE_PUTCHAR, ch, 0, 0);

}

void sbi_set_timer(unsigned long long stime_value) {

    sbi_call(SBI_SET_TIMER, stime_value, 0, 0);

}

核心函数sbi_call的基本用法就是把各个参数保存在指定的寄存器中,然后使用ecall指令,让OpenSBI执行对应系统调用编号的功能。
然后用sbi_console_putcharsbi_call这个函数的字符打印功能做了一个简单的封装。
然后逐层封装,直至产生一个和printf功能类似的函数cprintf在kern/libs/stdio.c

借助bootloader:OpenSBI初始化OS

在源代码根目录下输入命令make qemu即可运行操作系统
uCore OS(on RISC-V64)——LAB0.5:最小可执行内核_第1张图片

你可能感兴趣的:(操作系统,操作系统)