diff --git a/.cargo/config.toml b/.cargo/config.toml index cab8e03..8e84a7d 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,6 +2,7 @@ target = "riscv64.json" [unstable] +json-target-spec = true build-std = ["core", "compiler_builtins", "alloc"] build-std-features = ["compiler-builtins-mem"] diff --git a/crates/bffs/src/path.rs b/crates/bffs/src/path.rs index bb90751..37fd0dc 100644 --- a/crates/bffs/src/path.rs +++ b/crates/bffs/src/path.rs @@ -1,3 +1,8 @@ +use core::ops::Deref; + +use alloc::string::String; + +#[repr(transparent)] pub struct Path<'a> { inner: &'a str, } @@ -31,3 +36,13 @@ impl<'a> Path<'a> { !self.is_absolute() } } + +pub struct PathBuf { + inner: String, +} + +impl<'a> Deref for PathBuf { + type Target = Path<'a>; + + fn deref(&self) -> &Self::Target {} +} diff --git a/justfile b/justfile index e6f748b..0034426 100644 --- a/justfile +++ b/justfile @@ -1,5 +1,5 @@ release := "" -cargo_flags := "-Zjson-target-spec" + if release != "" { "--release" } else { "" } +cargo_flags := "" + if release != "" { "--release" } else { "" } bin_path := if release != "" { "target/riscv64/release" } else { "target/riscv64/debug" } default: run diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 5d56faf..f70d225 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,3 @@ [toolchain] channel = "nightly" +components = ["rust-src"] diff --git a/src/critical_section.rs b/src/critical_section.rs index 6a8ce1e..3255c4e 100644 --- a/src/critical_section.rs +++ b/src/critical_section.rs @@ -4,7 +4,9 @@ //! supervisor interrupts for short atomic regions. use critical_section::RawRestoreState; -use crate::riscv::{disable_supervisor_interrupt, get_supervisor_interrupt_state, restore_supervisor_interrupt}; +use crate::riscv::{ + disable_supervisor_interrupt, get_supervisor_interrupt_state, restore_supervisor_interrupt, +}; struct MyCriticalSection; critical_section::set_impl!(MyCriticalSection); diff --git a/src/fs.rs b/src/fs.rs index 44602ab..122a54d 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -2,17 +2,12 @@ //! //! Implements a minimal disk backend and exposes a global FILE_SYSTEM used by //! the kernel to load user binaries. -use core::{ - cell::{LazyCell, UnsafeCell}, - ops::Deref, -}; +use core::{cell::UnsafeCell, ops::Deref}; use bffs::{ io::{IoBase, Read, Seek}, - path::Path, Fat32FileSystem, }; -use hashbrown::HashMap; const DISK_ADDR: *const u8 = 0x9000_0000 as *const _; @@ -43,7 +38,7 @@ impl Deref for FSTemp { } } -pub static FILE_SYSTEM: FSTemp = FSTemp(UnsafeCell::new(None)); +pub static FAT32_FILE_SYSTEM: FSTemp = FSTemp(UnsafeCell::new(None)); #[derive(Debug)] /// Simple disk backend that reads from a fixed memory region. @@ -95,9 +90,3 @@ impl Read for Disk { Ok(size) } } - -pub struct KernelFDTable(LazyCell>>); - -unsafe impl Sync for KernelFDTable {} - -pub static KERNEL_FILE_DESCRIPTOR_TABLE: KernelFDTable = KernelFDTable(LazyCell::new(HashMap::new)); diff --git a/src/interrupt.rs b/src/interrupt.rs index 5a56c05..01fd2d1 100644 --- a/src/interrupt.rs +++ b/src/interrupt.rs @@ -13,7 +13,7 @@ use crate::{ process::{exit_process, sleep, ExecutionContext}, read_csr, riscv::disable_interrupt, - scheduler::scheduler_without_ret, + scheduler::SCHEDULER, set_csr, syscall, time::{setup_next_timer_interrupt, IRQ_M_TIMER}, write_csr, @@ -123,8 +123,14 @@ unsafe extern "C" fn supervisor_trap_handler( match syscall { SysCall::Open => { let path = unsafe { str::from_raw_parts(a1 as *const u8, a2 as usize) }; - let fd = syscall::open(path, false); - unsafe { (*interrupt_state).a[0] = fd.unwrap() }; + let virtual_node = syscall::open(path, false).unwrap(); + + let mut scheduler = SCHEDULER.lock(); + let current_process = scheduler.get_current_process(); + let fd = current_process.next_fd; + current_process.fd_table.insert(fd, virtual_node); + current_process.next_fd += 1; + unsafe { (*interrupt_state).a[0] = fd }; } SysCall::Alloc => { let layout = Layout::from_size_align(a1 as usize, a2 as usize).unwrap(); @@ -168,7 +174,7 @@ unsafe extern "C" fn supervisor_trap_handler( ); } timer_interrupt(); - scheduler_without_ret(&mut interrupt_state); + SCHEDULER.lock().schedule(&mut interrupt_state); } _ => {} } @@ -253,7 +259,7 @@ unsafe extern "C" fn _machine_mode_trap() { /// This stub saves the full register state and forwards control to /// `supervisor_trap_handler` implemented in Rust. unsafe extern "C" fn _supervisor_mode_trap() { - naked_asm!(concat!( + naked_asm!( " // Store sp before it gets modified sd sp, 8-264(sp) @@ -304,14 +310,14 @@ unsafe extern "C" fn _supervisor_mode_trap() { # Restore registers and sret jal restore_context" - )) + ) } #[unsafe(naked)] #[unsafe(no_mangle)] /// Restore a saved execution context and perform `sret` to return to user code. pub unsafe extern "C" fn restore_context(context: *const ExecutionContext) -> ! { - naked_asm!(concat!( + naked_asm!( " ld t0, 248(a0) csrw sepc, t0 @@ -351,7 +357,7 @@ pub unsafe extern "C" fn restore_context(context: *const ExecutionContext) -> ! ld a0, 32(a0) sret" - )) + ) } #[repr(C)] diff --git a/src/io.rs b/src/io.rs index 16db2ae..5b14239 100644 --- a/src/io.rs +++ b/src/io.rs @@ -4,8 +4,6 @@ //! macros for printing from kernel code. use crate::println; -use alloc::format; - use log::{Level, Metadata, Record}; use log::{LevelFilter, SetLoggerError}; @@ -58,7 +56,7 @@ pub fn init_log() -> Result<(), SetLoggerError> { #[macro_export] macro_rules! print { ($($args:expr),*) => { - $crate::io::print(format!($($args),*)) + $crate::io::print(alloc::format!($($args),*)) }; } #[macro_export] diff --git a/src/main.rs b/src/main.rs index b151f39..69d8d35 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ //! scheduler, and logging), and starts initial processes. #![no_std] #![no_main] -#![allow(static_mut_refs)] +// #![warn(clippy::pedantic)] #![feature( riscv_ext_intrinsics, const_trait_impl, @@ -20,11 +20,10 @@ use embedded_alloc::LlffHeap as Heap; use log::info; use crate::{ - fs::FILE_SYSTEM, + fs::FAT32_FILE_SYSTEM, io::init_log, - process::{create_process, create_process_from_file}, riscv::enable_supervisor_interrupt, - scheduler::{idle, scheduler_init}, + scheduler::{idle, SCHEDULER}, user::{proc2, test}, vga::{Color, Vga}, }; @@ -39,11 +38,13 @@ mod panic_handler; mod process; mod riscv; mod scheduler; +mod sync; mod syscall; mod time; mod uart; mod user; mod vga; +mod virtual_fs; pub const HEAP_SIZE: usize = 1024 * 1024 * 32; // 32Mo RAM #[global_allocator] @@ -58,17 +59,19 @@ pub extern "C" fn supervisor_mode_entry() { embedded_alloc::init!(HEAP, HEAP_SIZE); init_log().unwrap(); Vga::init(); - FILE_SYSTEM.init(); - scheduler_init(); + FAT32_FILE_SYSTEM.init(); + SCHEDULER.lock().init(); } info!("Hello World !"); unsafe { Vga::draw_string(10, 10, "Hello World !", Color::WHITE, Color::BLACK) }; - create_process(Box::new(test), "proc1"); - create_process(Box::new(proc2), "proc2"); + SCHEDULER.lock().create_process(Box::new(test), "proc1"); + SCHEDULER.lock().create_process(Box::new(proc2), "proc2"); - create_process_from_file("/usr/bin/test_pic"); + SCHEDULER + .lock() + .create_process_from_file("/usr/bin/test_pic"); enable_supervisor_interrupt(); idle(); diff --git a/src/process.rs b/src/process.rs index 90798db..9ec8d0d 100644 --- a/src/process.rs +++ b/src/process.rs @@ -16,10 +16,11 @@ use hashbrown::HashMap; use shared::syscall::exit; use crate::{ - fs::FILE_SYSTEM, + fs::FAT32_FILE_SYSTEM, println, - scheduler::{scheduler_without_ret, ACTIVE_PID, PROCESS_COUNT, PROCESS_TABLE}, + scheduler::{Scheduler, SCHEDULER}, time::elapsed_time_since_startup, + virtual_fs::VirtualNode, }; /// Size of the stack allocated to each process (in 64-bit words). @@ -83,7 +84,7 @@ pub struct Process { /// Current state of the process. pub state: ProcessState, /// Optional entry point for the process code. - pub entry: Option>, + pub entry: Option>, /// Wake time for sleeping processes. pub wake_time: Duration, /// Saved execution context. @@ -91,7 +92,37 @@ pub struct Process { /// Process stack. pub stack: [u64; STACK_SIZE], /// File descriptor table. - pub fd_table: HashMap> + pub fd_table: HashMap>, + /// Next available file descriptor. + pub next_fd: u64, +} + +unsafe impl Send for Process {} + +impl Default for Process { + fn default() -> Self { + Self { + pid: -1, + name: String::new(), + state: ProcessState::Dead, + wake_time: Duration::new(0, 0), + ctx: ExecutionContext { + ra: core::ptr::null(), + sp: core::ptr::null(), + gp: 0, + tp: 0, + a: [0; _], + t: [0; _], + s: [0; _], + mepc: core::ptr::null(), + mstatus: 0, + }, + stack: [0; _], + entry: None, + fd_table: HashMap::new(), + next_fd: 0, + } + } } impl core::fmt::Debug for Process { @@ -107,187 +138,185 @@ impl core::fmt::Debug for Process { } } -/// Creates a process from a binary file. -/// -/// # Arguments -/// -/// * `path` - Path to the executable binary file. -/// -/// # Returns -/// -/// Returns the PID of the created process, or -1 on failure. -/// -/// # Safety -/// -/// This function uses `unsafe` to transmute the file content into an -/// executable function. The binary must be in the correct format and -/// conform to the expected ABI. -/// Create a process from an executable binary located on the filesystem. -/// -/// Attempts to open `path`, load its contents into memory and create a new -/// kernel process that will execute the loaded binary. Returns the PID of the -/// created process, or -1 on failure. -pub fn create_process_from_file<'a, T: Into>>(path: T) -> i64 { - let path = path.into(); - let name = path.as_str(); +impl Scheduler { + /// Creates a process from a binary file. + /// + /// # Arguments + /// + /// * `path` - Path to the executable binary file. + /// + /// # Returns + /// + /// Returns the PID of the created process, or -1 on failure. + /// + /// # Safety + /// + /// This function uses `unsafe` to transmute the file content into an + /// executable function. The binary must be in the correct format and + /// conform to the expected ABI. + /// Create a process from an executable binary located on the filesystem. + /// + /// Attempts to open `path`, load its contents into memory and create a new + /// kernel process that will execute the loaded binary. Returns the PID of the + /// created process, or -1 on failure. + pub fn create_process_from_file<'a, T: Into>>(&mut self, path: T) -> i64 { + let path = path.into(); + let name = path.as_str(); - // Open and read the binary file - let mut bin = FILE_SYSTEM.open_file(path).unwrap(); - println!("Creating process"); - let mut content: Vec = Vec::new(); - bin.read_to_end(&mut content).unwrap(); + // Open and read the binary file + let mut bin = FAT32_FILE_SYSTEM.open_file(path).unwrap(); + println!("Creating process"); + let mut content: Vec = Vec::new(); + bin.read_to_end(&mut content).unwrap(); - println!( - "Loading binary at address: {:x?}, length: {}", - content.as_ptr(), - content.len() - ); + println!( + "Loading binary at address: {:x?}, length: {}", + content.as_ptr(), + content.len() + ); - // If ELF, use goblin to load PT_LOAD segments and apply relocations - if let Ok(gelf) = goblin::elf::Elf::parse(&content) { - println!("Parsed"); - // Determine memory bounds from program headers - let mut min_vaddr = u64::MAX; - let mut max_vaddr = 0u64; - for ph in gelf.program_headers.iter() { - if ph.p_type == goblin::elf::program_header::PT_LOAD && ph.p_memsz > 0 { - min_vaddr = core::cmp::min(min_vaddr, ph.p_vaddr); - max_vaddr = core::cmp::max(max_vaddr, ph.p_vaddr + ph.p_memsz); - } - } - - if min_vaddr != u64::MAX { - let size = (max_vaddr - min_vaddr) as usize; - use alloc::alloc::{alloc_zeroed, Layout}; - let layout = Layout::from_size_align(size, 0x1000).unwrap(); - let base = unsafe { alloc_zeroed(layout) }; - - // Copy segments + // If ELF, use goblin to load PT_LOAD segments and apply relocations + if let Ok(gelf) = goblin::elf::Elf::parse(&content) { + println!("Parsed"); + // Determine memory bounds from program headers + let mut min_vaddr = u64::MAX; + let mut max_vaddr = 0u64; for ph in gelf.program_headers.iter() { - if ph.p_type == goblin::elf::program_header::PT_LOAD { - let dst = unsafe { base.add((ph.p_vaddr - min_vaddr) as usize) }; - let src_off = ph.p_offset as usize; - let copy_len = ph.p_filesz as usize; - if copy_len > 0 { - unsafe { - core::ptr::copy_nonoverlapping( - content.as_ptr().add(src_off), - dst, - copy_len, - ) + if ph.p_type == goblin::elf::program_header::PT_LOAD && ph.p_memsz > 0 { + min_vaddr = core::cmp::min(min_vaddr, ph.p_vaddr); + max_vaddr = core::cmp::max(max_vaddr, ph.p_vaddr + ph.p_memsz); + } + } + + if min_vaddr != u64::MAX { + let size = (max_vaddr - min_vaddr) as usize; + use alloc::alloc::{alloc_zeroed, Layout}; + let layout = Layout::from_size_align(size, 0x1000).unwrap(); + let base = unsafe { alloc_zeroed(layout) }; + + // Copy segments + for ph in gelf.program_headers.iter() { + if ph.p_type == goblin::elf::program_header::PT_LOAD { + let dst = unsafe { base.add((ph.p_vaddr - min_vaddr) as usize) }; + let src_off = ph.p_offset as usize; + let copy_len = ph.p_filesz as usize; + if copy_len > 0 { + unsafe { + core::ptr::copy_nonoverlapping( + content.as_ptr().add(src_off), + dst, + copy_len, + ) + } } - } - if ph.p_memsz as usize > copy_len { - unsafe { - core::slice::from_raw_parts_mut( - dst.add(copy_len), - ph.p_memsz as usize - copy_len, - ) - .fill(0) + if ph.p_memsz as usize > copy_len { + unsafe { + core::slice::from_raw_parts_mut( + dst.add(copy_len), + ph.p_memsz as usize - copy_len, + ) + .fill(0) + } } } } - } - println!("Copied"); + println!("Copied"); - // Apply relocations using our parser (handles RELA entries) - for rela in gelf.dynrelas.iter() { - let r_type = rela.r_type; - match r_type { - x if x == R_RISCV_RELATIVE => { - let where_off = (rela.r_offset - min_vaddr) as usize; - let where_ptr = unsafe { base.add(where_off) } as *mut u64; - let val = (base as u64).wrapping_add(rela.r_addend.unwrap() as u64); - unsafe { core::ptr::write_unaligned(where_ptr, val) }; + // Apply relocations using our parser (handles RELA entries) + for rela in gelf.dynrelas.iter() { + let r_type = rela.r_type; + match r_type { + x if x == R_RISCV_RELATIVE => { + let where_off = (rela.r_offset - min_vaddr) as usize; + let where_ptr = unsafe { base.add(where_off) } as *mut u64; + let val = (base as u64).wrapping_add(rela.r_addend.unwrap() as u64); + unsafe { core::ptr::write_unaligned(where_ptr, val) }; + } + _ => {} } - _ => {} } - } - println!("Relocated"); + println!("Relocated"); - // Entry point - let entry_va = gelf.entry; - let entry_addr = unsafe { base.add((entry_va - min_vaddr) as usize) } as *const u8; - let entry_fn = - unsafe { core::mem::transmute::<*const u8, extern "C" fn()>(entry_addr) }; - let wrapper = Box::new(move || { - entry_fn(); - }); - println!("Program loaded at : {:x?}", entry_addr); - return create_process(wrapper, name); + // Entry point + let entry_va = gelf.entry; + let entry_addr = unsafe { base.add((entry_va - min_vaddr) as usize) } as *const u8; + let entry_fn = + unsafe { core::mem::transmute::<*const u8, extern "C" fn()>(entry_addr) }; + let wrapper = Box::new(move || { + entry_fn(); + }); + println!("Program loaded at : {:x?}", entry_addr); + return self.create_process(wrapper, name); + } + } + + // Fallback: treat the file as a raw binary blob and execute in-place + let entry_point = unsafe { + core::mem::transmute::<*const u8, extern "C" fn()>(Vec::leak(content).as_ptr()) + }; + let wrapper = Box::new(move || { + entry_point(); + }); + + self.create_process(wrapper, name) + } + + /// Creates a new process with the specified code and name. + /// + /// # Arguments + /// + /// * `code` - Static reference to the function to execute. + /// * `name` - Name of the process (for identification). + /// + /// # Returns + /// + /// Returns the PID of the created process, or -1 if the process table is full. + /// + /// # Safety + /// + /// This function manipulates the global process table and initializes + /// the execution context using unsafe operations. + /// Create a new process from a function pointer. + /// + /// The provided `code` function will be executed when the process is first + /// scheduled. Returns the new PID, or -1 if the process table is full. + pub fn create_process, F: Fn() + 'static + Send>( + &mut self, + code: Box, + name: T, + ) -> i64 { + // SAFETY: Initializing process in the global table. + // Access is safe because we verified bounds and found a Dead slot. + unsafe { + self.process_table + .insert(self.next_pid, Box::new(Process::default())); + let process = self.process_table.get_mut(&self.next_pid).unwrap(); + + // Configure process metadata + process.pid = self.next_pid as i64; + process.name = name.into(); + process.state = ProcessState::Activable; + process.entry = Some(code); + + // Configure execution context + // a0 contains the pointer to the function to execute + process.ctx.a[0] = &raw const *process.entry.as_ref().unwrap_unchecked() as u64; + + // mepc points to process_launcher which will call the function + process.ctx.mepc = process_launcher as *const _; + + // Configure mstatus for supervisor mode with interrupts enabled + process.ctx.mstatus = MSTATUS_SPP | MSTATUS_SPIE; + + // Initialize stack pointer at the top of the stack + process.ctx.sp = &raw const process.stack[STACK_SIZE - 1]; + + self.next_pid += 1; + + (self.next_pid - 1) as i64 } } - - // Fallback: treat the file as a raw binary blob and execute in-place - let entry_point = - unsafe { core::mem::transmute::<*const u8, extern "C" fn()>(Vec::leak(content).as_ptr()) }; - let wrapper = Box::new(move || { - entry_point(); - }); - - create_process(wrapper, name) } - -/// Creates a new process with the specified code and name. -/// -/// # Arguments -/// -/// * `code` - Static reference to the function to execute. -/// * `name` - Name of the process (for identification). -/// -/// # Returns -/// -/// Returns the PID of the created process, or -1 if the process table is full. -/// -/// # Safety -/// -/// This function manipulates the global process table and initializes -/// the execution context using unsafe operations. -/// Create a new process from a function pointer. -/// -/// The provided `code` function will be executed when the process is first -/// scheduled. Returns the new PID, or -1 if the process table is full. -pub fn create_process, F: Fn() + 'static>(code: Box, name: T) -> i64 { - // Search for a free slot in the process table - let mut next_pid = 0; - while next_pid < PROCESS_COUNT && unsafe { PROCESS_TABLE[next_pid].state != ProcessState::Dead } - { - next_pid += 1; - } - - // Check if a slot is available - if next_pid >= PROCESS_COUNT { - return -1; // Process table is full - } - - // SAFETY: Initializing process in the global table. - // Access is safe because we verified bounds and found a Dead slot. - unsafe { - let process = &mut PROCESS_TABLE[next_pid]; - - // Configure process metadata - process.pid = next_pid as i64; - process.name = name.into(); - process.state = ProcessState::Activable; - process.entry = Some(code); - - // Configure execution context - // a0 contains the pointer to the function to execute - process.ctx.a[0] = &raw const *process.entry.as_ref().unwrap_unchecked() as u64; - - // mepc points to process_launcher which will call the function - process.ctx.mepc = process_launcher as *const _; - - // Configure mstatus for supervisor mode with interrupts enabled - process.ctx.mstatus = MSTATUS_SPP | MSTATUS_SPIE; - - // Initialize stack pointer at the top of the stack - process.ctx.sp = &raw const process.stack[STACK_SIZE - 1]; - } - - next_pid as i64 -} - /// Entry point to launch a new process. /// /// This function is automatically called during the first scheduling @@ -334,12 +363,11 @@ extern "C" fn process_launcher(code: *const Box) { /// to select the next runnable process. This function does not return. pub fn exit_process(interrupt_context: &mut *mut ExecutionContext) { // SAFETY: ACTIVE_PID is maintained by the scheduler and is always valid. - unsafe { - PROCESS_TABLE[ACTIVE_PID].state = ProcessState::Dead; - } - + let mut scheduler = SCHEDULER.lock(); + let active_pid = scheduler.active_pid; + scheduler.process_table.remove(&active_pid).unwrap(); // Transfer control to the scheduler (does not return) - scheduler_without_ret(interrupt_context) + scheduler.schedule(interrupt_context) } /// Puts the active process to sleep for a specified duration. @@ -362,12 +390,11 @@ pub fn exit_process(interrupt_context: &mut *mut ExecutionContext) { /// reactivate the process when the wake time is reached. pub fn sleep(duration: Duration, interrupt_context: &mut *mut ExecutionContext) { // SAFETY: ACTIVE_PID is maintained by the scheduler and is always valid. - unsafe { - let process = &mut PROCESS_TABLE[ACTIVE_PID]; - process.wake_time = elapsed_time_since_startup() + duration; - process.state = ProcessState::Asleep; - } + let mut scheduler = SCHEDULER.lock(); + let process = scheduler.get_current_process(); + process.wake_time = elapsed_time_since_startup() + duration; + process.state = ProcessState::Asleep; // Transfer control to the scheduler (does not return) - scheduler_without_ret(interrupt_context) + scheduler.schedule(interrupt_context) } diff --git a/src/scheduler.rs b/src/scheduler.rs index 1d9c896..82cbbe6 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -3,50 +3,29 @@ //! //! This module exposes the global process table, the scheduler initialization //! and a simple round-robin scheduler used by the kernel. -use core::{arch::riscv64::wfi, array, cell::LazyCell, time::Duration}; +use core::{arch::riscv64::wfi, cell::LazyCell, ops::Bound}; -use alloc::{boxed::Box, string::String}; -use hashbrown::HashMap; +use alloc::{boxed::Box, collections::BTreeMap}; use log::info; use crate::{ - process::{create_process, ExecutionContext, Process, ProcessState}, + process::{ExecutionContext, Process, ProcessState}, + sync::Mutex, time, }; -/// Maximum number of simultaneous processes supported by the kernel. -pub const PROCESS_COUNT: usize = 16; +#[derive(Debug)] +pub struct Scheduler { + pub next_pid: u64, + pub active_pid: u64, + pub process_table: BTreeMap>, +} -/// Currently active PID. -/// -/// Updated by the scheduler when switching contexts. -pub static mut ACTIVE_PID: usize = 0; -/// Global process table stored in a lazily-initialized container. -/// -/// Each entry represents a process slot which may be `Dead`, `Activable`, -/// `Active` or `Asleep`. -pub static mut PROCESS_TABLE: LazyCell<[Process; PROCESS_COUNT]> = LazyCell::new(|| { - array::from_fn(|_| Process { - pid: -1, - name: String::new(), - state: ProcessState::Dead, - wake_time: Duration::new(0, 0), - ctx: ExecutionContext { - ra: core::ptr::null(), - sp: core::ptr::null(), - gp: 0, - tp: 0, - a: [0; _], - t: [0; _], - s: [0; _], - mepc: core::ptr::null(), - mstatus: 0, - }, - stack: [0; _], - entry: None, - fd_table: HashMap::new() - }) -}); +pub static SCHEDULER: Mutex> = Mutex::new(LazyCell::new(|| Scheduler { + next_pid: 0, + active_pid: 0, + process_table: BTreeMap::new(), +})); /// Idle loop executed when there is no runnable process. /// @@ -61,53 +40,60 @@ pub fn idle() { } } -/// Initialize the scheduler and create the idle process. -/// -/// Marks all process slots as `Dead` then creates the idle process and sets -/// it as the active process. -pub fn scheduler_init() { - info!("scheduler init"); - for pid in 0..PROCESS_COUNT { +impl Scheduler { + /// Initialize the scheduler and create the idle process. + /// + /// Marks all process slots as `Dead` then creates the idle process and sets + /// it as the active process. + pub fn init(&mut self) { + info!("scheduler init"); + self.create_process(Box::new(idle), "idle"); + self.process_table.get_mut(&0).unwrap().state = ProcessState::Active; + } + + /// Round-robin scheduler used to select the next runnable process. + /// + /// Saves the provided interrupt context into the previous process slot and + /// updates `ACTIVE_PID` to point to the chosen process. This function does + /// not return but instead updates `interrupt_state` to the context of the + /// next process to run. + pub fn schedule(&mut self, interrupt_state: &mut *mut ExecutionContext) { + // info!("scheduler"); unsafe { - PROCESS_TABLE[pid].state = ProcessState::Dead; + let prev_pid = self.active_pid; + if let Some(previous_process) = self.process_table.get_mut(&prev_pid) { + previous_process.ctx = **interrupt_state; + + if previous_process.state == ProcessState::Active { + previous_process.state = ProcessState::Activable; + } + } + + let mut current_process_iter = self + .process_table + .range_mut((Bound::Excluded(prev_pid), Bound::Unbounded)); + + self.active_pid = loop { + if let Some((pid, current_process)) = current_process_iter.next() { + if current_process.state == ProcessState::Asleep + && time::elapsed_time_since_startup() > current_process.wake_time + { + current_process.state = ProcessState::Activable; + } + if current_process.state == ProcessState::Activable { + current_process.state = ProcessState::Active; + *interrupt_state = &raw mut current_process.ctx; + break *pid; + }; + } else { + current_process_iter = self + .process_table + .range_mut((Bound::Unbounded, Bound::Included(prev_pid))) + } + }; } } - - create_process(Box::new(idle), "idle"); - unsafe { - PROCESS_TABLE[0].state = ProcessState::Active; - } -} - -/// Round-robin scheduler used to select the next runnable process. -/// -/// Saves the provided interrupt context into the previous process slot and -/// updates `ACTIVE_PID` to point to the chosen process. This function does -/// not return but instead updates `interrupt_state` to the context of the -/// next process to run. -pub fn scheduler_without_ret(interrupt_state: &mut *mut ExecutionContext) { - // info!("scheduler"); - unsafe { - let prev_pid = ACTIVE_PID; - PROCESS_TABLE[prev_pid].ctx = **interrupt_state; - - if PROCESS_TABLE[prev_pid].state == ProcessState::Active { - PROCESS_TABLE[prev_pid].state = ProcessState::Activable; - } - - loop { - if PROCESS_TABLE[ACTIVE_PID].state == ProcessState::Asleep - && time::elapsed_time_since_startup() > PROCESS_TABLE[ACTIVE_PID].wake_time - { - PROCESS_TABLE[ACTIVE_PID].state = ProcessState::Activable; - } - ACTIVE_PID = (ACTIVE_PID + 1) % PROCESS_COUNT; - if PROCESS_TABLE[ACTIVE_PID].state == ProcessState::Activable { - break; - } - } - PROCESS_TABLE[ACTIVE_PID].state = ProcessState::Active; - - *interrupt_state = &raw mut PROCESS_TABLE[ACTIVE_PID].ctx + pub fn get_current_process(&mut self) -> &mut Process { + self.process_table.get_mut(&self.active_pid).unwrap() } } diff --git a/src/sync.rs b/src/sync.rs new file mode 100644 index 0000000..4d3c178 --- /dev/null +++ b/src/sync.rs @@ -0,0 +1,114 @@ +use core::{ + arch::riscv64::wfi, + cell::UnsafeCell, + ops::{Deref, DerefMut}, + sync::atomic::{AtomicBool, Ordering}, +}; + +#[derive(Debug)] +pub struct Mutex { + locked: AtomicBool, + value: UnsafeCell, +} + +impl Mutex { + pub const fn new(value: T) -> Self { + Self { + locked: AtomicBool::new(false), + value: UnsafeCell::new(value), + } + } + + pub fn lock(&self) -> MutexGuard<'_, T> { + // Lock + while self + .locked + .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) + .is_err() + { + unsafe { wfi() }; + } + MutexGuard { mutex: self } + } +} + +unsafe impl Send for Mutex {} +unsafe impl Sync for Mutex {} + +pub struct MutexGuard<'a, T> { + mutex: &'a Mutex, +} + +impl Drop for MutexGuard<'_, T> { + fn drop(&mut self) { + self.mutex.locked.store(false, Ordering::Relaxed); + } +} + +impl<'a, T> Deref for MutexGuard<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + unsafe { &*self.mutex.value.get() } + } +} + +impl<'a, T> DerefMut for MutexGuard<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { &mut *self.mutex.value.get() } + } +} + +// union Data { +// value: ManuallyDrop, +// f: ManuallyDrop, +// } + +// pub struct LazyLock T> { +// init: UnsafeCell, +// data: UnsafeCell>, +// } + +// impl T> LazyLock { +// pub const fn new(f: F) -> Self { +// Self { +// init: UnsafeCell::new(false), +// data: UnsafeCell::new(Data { +// f: ManuallyDrop::new(f), +// }), +// } +// } + +// pub fn force(&self) -> &T { +// unsafe { +// critical_section::with(|_| { +// if !*self.init.get() { +// *(*self.data.get()).value = ((*self.data.get()).f)(); +// *self.init.get() = true; +// } +// }); + +// &(*self.data.get()).value +// } +// } +// pub fn force_mut(&mut self) -> &mut T { +// self.force(); + +// unsafe { &mut self.data.get_mut().value } +// } +// } + +// impl T> Deref for LazyLock { +// type Target = T; + +// fn deref(&self) -> &Self::Target { +// self.force() +// } +// } +// impl T> DerefMut for LazyLock { +// fn deref_mut(&mut self) -> &mut Self::Target { +// self.force_mut() +// } +// } + +// unsafe impl Sync for LazyLock {} diff --git a/src/syscall.rs b/src/syscall.rs index aa08ab7..1722d16 100644 --- a/src/syscall.rs +++ b/src/syscall.rs @@ -1,8 +1,12 @@ use core::alloc::Layout; +use alloc::boxed::Box; use bffs::{error::Error, path::Path}; -use crate::fs::{Disk, FILE_SYSTEM}; +use crate::{ + fs::{Disk, FAT32_FILE_SYSTEM}, + virtual_fs::VirtualNode, +}; pub unsafe fn alloc(layout: Layout) -> *mut u8 { unsafe { alloc::alloc::alloc(layout) } @@ -15,13 +19,13 @@ pub unsafe fn dealloc(ptr: *mut u8, layout: core::alloc::Layout) { pub fn open<'a, P: Into>>( path: P, in_kernel: bool, -) -> Result::Error>> { +) -> Result, Error<::Error>> { let path = path.into(); let file = match path.split_path() { ("dev", path) => { - unimplemented!() + todo!() } - _ => FILE_SYSTEM.open_file(path)?, + _ => FAT32_FILE_SYSTEM.open_file(path)?, }; - Ok(42) + todo!() } diff --git a/src/time.rs b/src/time.rs index c6396ea..1846fc7 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,7 +1,10 @@ //! Time and timer interrupt management for the kernel. -use core::time::Duration; use alloc::format; +use core::{ + sync::atomic::{AtomicU64, Ordering}, + time::Duration, +}; use crate::{ set_csr, @@ -21,16 +24,16 @@ const CLINT_TIMER: *const u64 = 0x0200_bff8 as *const u64; /// The hardware timer frequency (Hz). const TIMER_FREQUENCY: u64 = 10_000_000; // 10 MHz /// The frequency at which timer interrupts should occur (Hz). -const INTERRUPT_FREQUENCY: u64 = 20; // 20 Hz +const INTERRUPT_FREQUENCY: u64 = 20; // 20 Hz /// Stores the instant when the kernel started. -static mut START_TIME: Instant = Instant(0); +static START_TIME: AtomicU64 = AtomicU64::new(0); /// Initializes the timer interrupt system and records the kernel start time. /// /// This should be called once during kernel initialization. pub fn setup_timer_interrupt() { - unsafe { START_TIME = Instant::now(); } + START_TIME.store(Instant::now().0, Ordering::Relaxed); set_csr!(sie, IRQ_S_TIMER); setup_next_timer_interrupt(); } @@ -69,7 +72,8 @@ pub fn timer_interrupt() { /// Returns the duration since the kernel was started. pub fn elapsed_time_since_startup() -> Duration { - unsafe { START_TIME.elapsed() } + let start_instant = Instant(START_TIME.load(Ordering::Relaxed)); + start_instant.elapsed() } /// Represents a point in time, based on the hardware timer. diff --git a/src/user.rs b/src/user.rs index b5f4e98..0e0e3f3 100644 --- a/src/user.rs +++ b/src/user.rs @@ -4,16 +4,9 @@ //! and the scheduler. use core::time::Duration; -use shared::syscall::{sleep, write_int_temp, write_string_temp}; - -#[repr(align(32))] -struct Alignement([u8; include_bytes!("../user/test_pic/test_pic.mem").len()]); - -static PROG: Alignement = Alignement(*include_bytes!("../user/test_pic/test_pic.mem")); +use shared::syscall::{sleep, write_string_temp}; pub fn test() { - write_int_temp(PROG.0.as_ptr() as u64); - // unsafe { core::mem::transmute::<*const u8, extern "C" fn()>(PROG.0.as_ptr())() } loop { write_string_temp("test"); sleep(Duration::new(2, 0)); diff --git a/src/virtual_fs.rs b/src/virtual_fs.rs new file mode 100644 index 0000000..32c5532 --- /dev/null +++ b/src/virtual_fs.rs @@ -0,0 +1,21 @@ +use alloc::boxed::Box; +use bffs::path::Path; +use hashbrown::HashMap; + +pub trait VirtualNode { + fn read(&mut self, buf: &mut [u8]) -> Result; +} + +pub trait VirtualFileSystem { + fn open<'a, P: Into>>(path: P) -> Result, ()>; +} + +pub struct MainFileSystem { + mounts: HashMap +} + +impl VirtualFileSystem for MainFileSystem { + fn open<'a, P: Into>>(path: P) -> Result, ()> { + todo!() + } +}