uCore OS(on RISC-V64) - LAB0.5: minimum executable kernel

Keywords: Operating System

Experimental purpose

  1. Gradually master the following process:

  2. How the source code is compiled into an executable file.

  3. How the computer loads the operating system after compiling into an executable file.

  4. After loading, where to run the operating system.

  5. How is the output information of the operating system output.

Experimental content

  1. Follow the steps of the experimental instruction and read the frame code.
  2. Combined with the framework code, deeply understand RISC-v.
  3. Kernel memory layout and entry point settings
  4. The input and output functions are encapsulated by sbi
  5. Initialize the OS with bootloader:OpenSBI and complete the exercise.
  6. Write the experimental report as required.

Memory layout and entry point settings

QEMU simulator provides a communication function of RISC-V CPU, physical memory and bus.

At this time, the operating system kernel on the hard disk needs to be loaded into memory for the execution of the operating system.

This is done by bootloader, which is responsible for booting and loading the operating system into the kernel. The firmware OpenSBI is provided in QEMU to complete this work.

The entry point of the whole kernel is kern/init/entry.S

#include <mmu.h>
#include <memlayout.h>

    .section .text,"ax",%progbits
    .globl kern_entry
kern_entry:
    la sp, bootstacktop	//la,load address, that is, the return address is saved in the sp register
    
    tail kern_init		//Tail is the abbreviation of tail call, a pseudo instruction of RISC-V, which is equivalent to a function call
    
.section .data
    # .align 2^12
    .align PGSHIFT
    .global bootstack
bootstack:
    .space KSTACKSIZE
    .global bootstacktop
bootstacktop:

The unique function Kern was called at the kernel entry point_ 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);
}

This function prints a string "(THU.CST) os is loading... \ n" and then enters the cycle stage. The function that prints this string is cprintf. The reason why it does not choose to use the printf function directly is that the standard library function of C language depends on the runtime environment provided by glibc.

The input and output functions are encapsulated by sbi

Firstly, the system call instruction ecall (environment call) is used to print the string. But C language can't write assembly code directly. It needs to be added to the program__ asm__ The volatile keyword implements inline assembly.

// libs/sbi.c

#include <sbi.h>
#include <defs.h>
//SBI number corresponds to function
//Numbers 0-8 are handled by OpenSBI, otherwise they are handed over to the interrupt handler
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;
//Core function
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" //Put the value of the parameter into the register
        "ecall\n"           //Pass ecall to OpenSBI for execution
        "mv %[ret_val], x10"
        //OpenSBI puts the return value into the x10 register according to the calling convention of riscv
        : [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);

}

Core function SBI_ The basic usage of call is to save each parameter in the specified register, and then use the ecall instruction to let OpenSBI execute the function corresponding to the system call number.
Then use sbi_console_putchar will sbi_call is a simple encapsulation of the character printing function of this function.
Then it is encapsulated layer by layer until a function similar to printf is generated. cprintf is in kern/libs/stdio.c

Initialize OS with bootloader:OpenSBI

Enter the command make qemu in the source code root directory to run the operating system

Posted by Crayon Violent on Thu, 30 Sep 2021 19:26:58 -0700