osblog RISC-V Operating System
repository·master·Indexed 20 days ago
https://github.com/sgmarz/osblogA project dedicated to building a RISC-V operating system using the Rust programming language. The implementation includes a kernel with memory management (kmalloc, kzmalloc), a page allocator, UART serial communication drivers, and support for transitioning from Machine Mode to Supervisor Mode using Sv39 paging. The repository contains the core Rust implementation, assembly components, and C++ userspace applications.
What's inside osblog
- osblog is a project focused on the development of a RISC-V operating system written in Rust. The repository is organized into specific directories for the kernel, assembly components, and userspace applications.
Explore the RISC-V OS structure
masterThe RISC-V operating system implementation is divided into the following directory structure:
risc_v/src: The core RISC-V operating system implementation written in Rust.risc_v/src/asm: Assembly language components required for the OS.risc_v/userspace: C++ programs designed to run in the userspace of the OS.
Create release builds for better performance
masterRelease builds enable the optimizer, making the OS run significantly faster. Use the
--releaseflag with standard cargo commands to perform a release build or run a release build.cargo build --release cargo run --releaseBuild and run the RISC-V OS
masterBefore building, ensure
.cargo/configis edited to match your host's configuration.- Use
cargo buildto compile the project. - Use
cargo runto compile and execute the OS using the runner defined in.cargo/config.
cargo build cargo run- Use
Create the required hard drive file
masterThe current configuration requires a hard drive file named
hdd.dskin the project directory. You can create an empty 32MB file usingfallocate.fallocate -l 32M hdd.dskInstall prerequisites for RISC-V development
masterTo build and run the RISC-V OS, you must install the
riscv64gc-unknown-none-elftarget viarustupand thecargo-binutilssuite viacargo.rustup target add riscv64gc-unknown-none-elf cargo install cargo-binutilsUse TrapFrame for context switching
masterThe
TrapFramestruct is used to store the CPU state during interrupts or exceptions. It is designed to be packed into themscratchregister for quick access during context switches.Fields include:
regs: General purpose registers (0-255).fregs: Floating point registers (256-511).satp: The supervisor address translation and protection register value.trap_stack: A pointer to the trap stack.hartid: The hardware thread ID.
You can initialize a zeroed frame using
TrapFrame::zero().Note: The kernel maintains a global array
KERNEL_TRAP_FRAMEcontaining 8 frames, one for each CPU hart.#[repr(C)] #[derive(Clone, Copy)] pub struct TrapFrame { pub regs: [usize; 32], pub fregs: [usize; 32], pub satp: usize, pub trap_stack: *mut u8, pub hartid: usize, } impl TrapFrame { pub const fn zero() -> Self { ... } }Understand `ProcessState` lifecycle
masterA process in the system can exist in one of the following states:
Running: The process is eligible to be executed by the scheduler.Sleeping: The process is waiting for a specific amount of time to pass.Waiting: The process is blocked waiting for I/O operations.Dead: The process is marked for cleanup and should be removed from the process list.
#[repr(u8)] pub enum ProcessState { Running, Sleeping, Waiting, Dead, }Manage RISC-V MMU modes with SatpMode
masterThe
SatpModeenum defines the available Memory Management Unit (MMU) modes for 64-bit RISC-V. These modes determine whether protection and translation are active and the size of the virtual address space.Off(0): MMU is off. Physical Address (PA) equals Virtual Address (VA).Sv39(8): 39-bit virtual addresses.Sv48(9): 48-bit virtual addresses.
#[repr(usize)] pub enum SatpMode { Off = 0, Sv39 = 8, Sv48 = 9, }The `kmem` module as a `GlobalAlloc`
masterThe
kmemmodule implements theGlobalAlloctrait via theOsGlobalAllocstruct, which is registered as the system's#[global_allocator]. This allows the use of standard Rust collection types (likeVec,BTreeMap, etc.) that rely on the global allocator.allocuseskzmalloc.deallocuseskfree.
If the global allocator fails to satisfy a request, the
alloc_errorhandler is triggered, which will panic with a message describing the failed size and alignment.Manage process states with `ProcessState`
masterProcesses in this system transition between several states. While the state is managed within the
Processstruct, understanding these states is critical for implementing a scheduler:Running: The process is currently being executed by a CPU.Sleeping: The process is waiting for a specific amount of time to pass.Waiting: The process is blocked waiting for I/O operations.Dead: The process is marked for cleanup and removal from the process list.
Choose between spin_lock and sleep_lock
masterThe
Muteximplementation provides two distinct ways to acquire a lock, and choosing the wrong one can lead to system deadlocks:spin_lock(&mut self): This is a busy-wait lock. It continuously callstry_lockin a loop until successful.- Use case: Safe to use inside interrupt contexts.
- Warning: Do not use this for long-held locks as it wastes CPU cycles.
sleep_lock(&mut self): This is a yielding lock. If the lock is unavailable, it callssyscall_sleepwithDEFAULT_LOCK_SLEEP(10,000) before retrying.- Use case: Use for standard process-level synchronization.
- CRITICAL WARNING: Do NOT use
sleep_lockinside an interrupt context. Additionally, never use a sleep lock for the process list, because sleeping requires the process list to function; attempting to do so will cause a deadlock.
// Correct usage for interrupts fn interrupt_handler() { let mut lock = get_lock(); lock.spin_lock(); // ... lock.unlock(); } // Correct usage for processes fn process_task() { let mut lock = get_lock(); lock.sleep_lock(); // ... lock.unlock(); }