contributed by <leoyehx
>
RVOS is a 32-bit real-time multitasking operating system built from scratch. It is designed to run on QEMU with RISC-V.
Since we plan to build an operating system based on QEMU and RISC-V, we need to download the necessary development tools.
Then add llvm binutils
to the PATH
When we press the power button, the CPU executes the first instruction at a default address. It then begins loading the BIOS ROM (or UEFI firmware).
BIOS or UEFI then checks and initializes all hardware components, including memory, keyboard, display card, etc. After the hardware check, it loads the bootloader.
The Supervisor Binary Interface (SBI) is an API for OS kernels, but defines what the firmware provides to an OS. A famous SBI implementation is OpenSBI. In QEMU, OpenSBI starts by default, performs hardware-specific initialization, and boots the kernel.
In this phase, the bootloader reads the OS kernel and sets the kernel parameters. It then jumps to the kernel's entry point.
A linker script is a file which defines the memory layout of executable files. Based on the layout, the linker assigns memory addresses to functions and variables.
If we refer to the memory layout of C programs, it will be very clear.
First, the entry point of the kernel is the boot
function at the base address 0x80200000
. ENTRY(boot)
declares that the boot function is the entry point of the program. Then, the placement of each section is defined within the SECTIONS
block.
SECTION | Description |
---|---|
.text |
The code of the program |
.rodata |
Constant data that is read-only |
.data |
Read/Write data |
.bss |
Read/Write data with an initial value of zero at execution time |
The *(.text .text.*)
directive places the .text section and any sections starting with .text.
from all files (*
) at that location. The .
symbol represents the current address. It automatically increments as data is placed, such as with *(.text).
The statement . += 128 * 1024
means advance the current address by 128KB. The ALIGN(4)
directive ensures that the current address is adjusted to a 4-byte boundary.
The minimum kernel is as below.
The execution of the kernel starts from the boot
function, which is specified as the entry point in the linker script. In this function, the stack pointer (sp
) is set to the end address of the stack area defined in the linker script. Then, it jumps to the kernel_main
function. The boot function has two special attributes.
__attribute__((naked))
attribute instructs the compiler not to generate unnecessary code before and after the function body. This ensures that the inline assembly code is the exact function body.__attribute__((section(".text.boot")))
attribute, which controls the placement of the function in the linker script.At the beginning of the file, each symbol defined in the linker script is declared using extern char
.
Since __bss
alone means the value at the 0th byte of the .bss
section, we add []
to ensure that __bss
returns an address and prevent any careless mistakes.
bss
The .bss
section is first initialized to zero using the memset
function. Finally, the function enters an infinite loop and the kernel terminates.