osblog RISC-V Operating System

repository·master·Indexed 20 days ago

https://github.com/sgmarz/osblog

A 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.

Tokens
32.1K
Snippets
181
Records
208
Agent score
67%

What's inside osblog

  1. Overview of the osblog repository

    master
    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.
  2. Explore the RISC-V OS structure

    master

    The 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.
  3. Create release builds for better performance

    master

    Release builds enable the optimizer, making the OS run significantly faster. Use the --release flag with standard cargo commands to perform a release build or run a release build.

    cargo build --release
    cargo run --release
  4. Build and run the RISC-V OS

    master

    Before building, ensure .cargo/config is edited to match your host's configuration.

    • Use cargo build to compile the project.
    • Use cargo run to compile and execute the OS using the runner defined in .cargo/config.
    cargo build
    cargo run
  5. Install prerequisites for RISC-V development

    master

    To build and run the RISC-V OS, you must install the riscv64gc-unknown-none-elf target via rustup and the cargo-binutils suite via cargo.

    rustup target add riscv64gc-unknown-none-elf
    cargo install cargo-binutils
  6. Use TrapFrame for context switching

    master

    The TrapFrame struct is used to store the CPU state during interrupts or exceptions. It is designed to be packed into the mscratch register 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_FRAME containing 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 { ... }
    }
  7. Understand `ProcessState` lifecycle

    master

    A 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,
    }
  8. Manage RISC-V MMU modes with SatpMode

    master

    The SatpMode enum 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,
    }
  9. The `kmem` module as a `GlobalAlloc`

    master

    The kmem module implements the GlobalAlloc trait via the OsGlobalAlloc struct, which is registered as the system's #[global_allocator]. This allows the use of standard Rust collection types (like Vec, BTreeMap, etc.) that rely on the global allocator.

    • alloc uses kzmalloc.
    • dealloc uses kfree.

    If the global allocator fails to satisfy a request, the alloc_error handler is triggered, which will panic with a message describing the failed size and alignment.

  10. Manage process states with `ProcessState`

    master

    Processes in this system transition between several states. While the state is managed within the Process struct, 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.
  11. Choose between spin_lock and sleep_lock

    master

    The Mutex implementation provides two distinct ways to acquire a lock, and choosing the wrong one can lead to system deadlocks:

    1. spin_lock(&mut self): This is a busy-wait lock. It continuously calls try_lock in 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.
    2. sleep_lock(&mut self): This is a yielding lock. If the lock is unavailable, it calls syscall_sleep with DEFAULT_LOCK_SLEEP (10,000) before retrying.

      • Use case: Use for standard process-level synchronization.
      • CRITICAL WARNING: Do NOT use sleep_lock inside 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();
    }