Besides displaying "Hello, world!", there are some additional information. Finally, the machine shuts down .
Outline
Objectives and Ideas
Requirements
Practice Steps
4. Code Structure
Memory Layout
Startup Process Verification based on GDB
Function Calls
LibOS Initialization
SBI Calls
Code structure of LibOS
./os/src
Rust 4 Files 119 Lines
Assembly 1 Files 11 Lines
├── bootloader (implemented by the SBI running at the M privilege level. We use RustSBI in this project)
│ ├── rustsbi-k210.bin (precompiled binary version that can run on the k210 development board)
│ └── rustsbi-qemu.bin (precompiled binary version that can run on QEMU)
LibOS code structure
├── os (our kernel implementation is placed in the os directory)
│ ├── Cargo.toml (some configurations for the kernel implementation)
│ ├── Makefile
│ └── src (the kernel source code is placed in the os/src directory)
│ ├── console.rs (to further encapsulate the SBI interface of printing characters to achieve more powerful formatted output)
│ ├── entry.asm (a piece of assembly code to set the kernel execution environment)
│ ├── lang_items.rs (some semantic items we need to provide to the Rust compiler, currently contains the processing logic when the kernel panics)
│ ├── linker-qemu.ld (the linker script that controls the kernel memory layout to make the kernel run on the qemu virtual machine)
│ ├── main.rs (the main function of the kernel)
│ └── sbi.rs (call the SBI interface provided by the underlying SBI implementation)
Outline
Objectives and Ideas
Requirements
Practice Steps
Code Structure
5. Memory Layout
Startup Process Verification based on GDB
Function Calls
LibOS Initialization
SBI Calls
App/OS Memory Layout
BSS Segment
Block Started by Symbol (BSS)
BSS segement usually refers to a piece of memory used to store uninitialized global variables in the program
belongs to static memory allocation
Data Segment
usually refers to a piece of memory used to store initialized global variables in the program
belongs to static memory allocation
Text Segment
Code segment (code segment/text segment) refers to a piece of memory that stores executive code
fixed memory size, usually read-only
It's possible to contain some read-only constant variables
Heap
used for dynamic allocation, can be dynamically expanded or reduced
The program calls functions such as malloc to allocate new memory that is dynamically added to the heap
The program calls functions such as free to remove memory from the heap
Stack
A piece of memory to store the local variable temporarily created by the program
When a function is called, its parameters and return value will also be placed on the stack
Due to the first-in last-out feature of the stack, the stack is particularly convenient for preserving/restoring the current execution state
Stack
The stack can be regarded as a memory area for registering and exchanging temporary data
Difference between OS programming and application programming: OS programming requires to understand the physical memory structure on the stack and the machine-level content (related registers and instructions).
auipc (add upper immediate to pc): used to construct a PC-relative address by adding a U-type immediate value. The lower 12 bits are filled with 0, and the upper 20 bits are used as U-type immediate data to create a 32-bit offset. This offset is then added to the current PC value, and the resulting value is saved in register x1.
Function Call Jump Instructions
The pseudo-instruction, ret, is translated as jalr x0, 0(x1), which means jumping to the address saved by the register ra (i.e., x1). Documentation of RISC-V Assembly
When the function is called, saving the return address and jumping by the call pseudo-instruction;
When the function returns, returning to the next instruction before the jump through the ret pseudo-instruction to continue execution
Function calling convention
The function calling convention stipulates how to implement the function call of a certain programming language on a certain instruction set architecture, including:
How to pass the input parameters and return values of the function;
The division of caller/callee preserved registers in the function call context;
Other methods of using registers in the function call process.
RISC-V Function Calling Convention: call parameters and transfer return values
RISC-V32: If the return value is 64bit, use a0~a1 to place it
RISC-V64: If the return value is 64bit, use a0 to place it
Risc-V Function Calling Convention: Stack Frame
RISC-V function calling convention: Stack Frame
Stack Frames
return address *
previous fp
saved registers
local variables
…
return address fp register
previous fp (pointed to *)
saved registers
local variables
… sp register
RISC-V Function Calling Convention: Stack Frame
Stack frames may have different sizes and content, but the overall structure is similar
Each stack frame starts with the return value of this function and the fp value of the previous function
The sp register always points to the bottom of the current stack frame
The fp register always points to the top of the current stack frame
RISC-V Function Calling Convention: ret Instruction
When the ret instruction is executed, the following pseudo-code adjusts the stack pointer and PC:
RISC-V Function Calling Convention: Function Structure
Function structure: prologue, body part and epilogue
Prologue is to preserve the execution status of the program (preserve the register with return address and FP register)
Epilogue is to restore the preserved execution status of the program after the execution of a function (jump to the preserved return address and restore the preserved FP register)
RISC-V Function Calling Convention: Function Structure
Function structure: prologue, body part and epilogue
.global sum_then_double
sum_then_double:
addi sp, sp, -16 # prologue
sd ra, 0(sp)
call sum_to # body part
li t0, 2
mul a0, a0, t0
ld ra, 0(sp) # epilogue
addi sp, sp, 16
ret
RISC-V Function Calling Convention: Function Structure
Function structure: prologue, body part and epilogue
.global sum_then_double
sum_then_double:
call sum_to # body part
li t0, 2
mul a0, a0, t0
ret
Q: what's the difference between the above code and the code on the previous page?
In the linker script linker.ld, the .bss.stack section will eventually be combined into the .bss segment
The .bss segment generally places data that needs to be initialized to zero
Transfer of Control: ASM --> Rust/C
Transfer control to Rust code, the entry point is the rust_main function in main.rs
panic! and println! are macros (like C macro), and ! means macro
Handle Error Panic Gracefully
#[panic_handler]fnpanic(info: &PanicInfo) -> ! { //PnaicInfo is a structure typeifletSome(location) = info.location() { //Does the error location exist?println!(
"Panicked at {}:{} {}",
location.file(), //error file name
location.line(), //The number of line in the error file
info.message().unwrap() //error information
);
} else {
println!("Panicked: {}", info. message(). unwrap());
}
shutdown() //shutdown
}
[RustSBI output]
Hello, world!
Panicked at src/main.rs:26 Shutdown machine!
Summary
Knowledge points that need to be mastered in the practice of constructing various OS (principles & implementation)
Understand the relationship among Compiler/OS/Machine
Know the process from machine startup to execute an application to print out strings
Can write Trilobita OS
https://blog.51cto.com/onebig/2551726
(In-depth understanding of computer systems) bss segment, data segment, text segment, heap (heap) and stack (stack)