Sync computers

This commit is contained in:
2026-03-01 15:41:36 +01:00
parent 783c76252a
commit 392af94345
16 changed files with 479 additions and 315 deletions

View File

@@ -2,6 +2,7 @@
target = "riscv64.json" target = "riscv64.json"
[unstable] [unstable]
json-target-spec = true
build-std = ["core", "compiler_builtins", "alloc"] build-std = ["core", "compiler_builtins", "alloc"]
build-std-features = ["compiler-builtins-mem"] build-std-features = ["compiler-builtins-mem"]

View File

@@ -1,3 +1,8 @@
use core::ops::Deref;
use alloc::string::String;
#[repr(transparent)]
pub struct Path<'a> { pub struct Path<'a> {
inner: &'a str, inner: &'a str,
} }
@@ -31,3 +36,13 @@ impl<'a> Path<'a> {
!self.is_absolute() !self.is_absolute()
} }
} }
pub struct PathBuf {
inner: String,
}
impl<'a> Deref for PathBuf {
type Target = Path<'a>;
fn deref(&self) -> &Self::Target {}
}

View File

@@ -1,5 +1,5 @@
release := "" 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" } bin_path := if release != "" { "target/riscv64/release" } else { "target/riscv64/debug" }
default: run default: run

View File

@@ -1,2 +1,3 @@
[toolchain] [toolchain]
channel = "nightly" channel = "nightly"
components = ["rust-src"]

View File

@@ -4,7 +4,9 @@
//! supervisor interrupts for short atomic regions. //! supervisor interrupts for short atomic regions.
use critical_section::RawRestoreState; 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; struct MyCriticalSection;
critical_section::set_impl!(MyCriticalSection); critical_section::set_impl!(MyCriticalSection);

View File

@@ -2,17 +2,12 @@
//! //!
//! Implements a minimal disk backend and exposes a global FILE_SYSTEM used by //! Implements a minimal disk backend and exposes a global FILE_SYSTEM used by
//! the kernel to load user binaries. //! the kernel to load user binaries.
use core::{ use core::{cell::UnsafeCell, ops::Deref};
cell::{LazyCell, UnsafeCell},
ops::Deref,
};
use bffs::{ use bffs::{
io::{IoBase, Read, Seek}, io::{IoBase, Read, Seek},
path::Path,
Fat32FileSystem, Fat32FileSystem,
}; };
use hashbrown::HashMap;
const DISK_ADDR: *const u8 = 0x9000_0000 as *const _; 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)] #[derive(Debug)]
/// Simple disk backend that reads from a fixed memory region. /// Simple disk backend that reads from a fixed memory region.
@@ -95,9 +90,3 @@ impl Read for Disk {
Ok(size) Ok(size)
} }
} }
pub struct KernelFDTable(LazyCell<HashMap<u64, Path<'static>>>);
unsafe impl Sync for KernelFDTable {}
pub static KERNEL_FILE_DESCRIPTOR_TABLE: KernelFDTable = KernelFDTable(LazyCell::new(HashMap::new));

View File

@@ -13,7 +13,7 @@ use crate::{
process::{exit_process, sleep, ExecutionContext}, process::{exit_process, sleep, ExecutionContext},
read_csr, read_csr,
riscv::disable_interrupt, riscv::disable_interrupt,
scheduler::scheduler_without_ret, scheduler::SCHEDULER,
set_csr, syscall, set_csr, syscall,
time::{setup_next_timer_interrupt, IRQ_M_TIMER}, time::{setup_next_timer_interrupt, IRQ_M_TIMER},
write_csr, write_csr,
@@ -123,8 +123,14 @@ unsafe extern "C" fn supervisor_trap_handler(
match syscall { match syscall {
SysCall::Open => { SysCall::Open => {
let path = unsafe { str::from_raw_parts(a1 as *const u8, a2 as usize) }; let path = unsafe { str::from_raw_parts(a1 as *const u8, a2 as usize) };
let fd = syscall::open(path, false); let virtual_node = syscall::open(path, false).unwrap();
unsafe { (*interrupt_state).a[0] = fd.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 => { SysCall::Alloc => {
let layout = Layout::from_size_align(a1 as usize, a2 as usize).unwrap(); 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(); 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 /// This stub saves the full register state and forwards control to
/// `supervisor_trap_handler` implemented in Rust. /// `supervisor_trap_handler` implemented in Rust.
unsafe extern "C" fn _supervisor_mode_trap() { unsafe extern "C" fn _supervisor_mode_trap() {
naked_asm!(concat!( naked_asm!(
" "
// Store sp before it gets modified // Store sp before it gets modified
sd sp, 8-264(sp) sd sp, 8-264(sp)
@@ -304,14 +310,14 @@ unsafe extern "C" fn _supervisor_mode_trap() {
# Restore registers and sret # Restore registers and sret
jal restore_context" jal restore_context"
)) )
} }
#[unsafe(naked)] #[unsafe(naked)]
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
/// Restore a saved execution context and perform `sret` to return to user code. /// Restore a saved execution context and perform `sret` to return to user code.
pub unsafe extern "C" fn restore_context(context: *const ExecutionContext) -> ! { pub unsafe extern "C" fn restore_context(context: *const ExecutionContext) -> ! {
naked_asm!(concat!( naked_asm!(
" "
ld t0, 248(a0) ld t0, 248(a0)
csrw sepc, t0 csrw sepc, t0
@@ -351,7 +357,7 @@ pub unsafe extern "C" fn restore_context(context: *const ExecutionContext) -> !
ld a0, 32(a0) ld a0, 32(a0)
sret" sret"
)) )
} }
#[repr(C)] #[repr(C)]

View File

@@ -4,8 +4,6 @@
//! macros for printing from kernel code. //! macros for printing from kernel code.
use crate::println; use crate::println;
use alloc::format;
use log::{Level, Metadata, Record}; use log::{Level, Metadata, Record};
use log::{LevelFilter, SetLoggerError}; use log::{LevelFilter, SetLoggerError};
@@ -58,7 +56,7 @@ pub fn init_log() -> Result<(), SetLoggerError> {
#[macro_export] #[macro_export]
macro_rules! print { macro_rules! print {
($($args:expr),*) => { ($($args:expr),*) => {
$crate::io::print(format!($($args),*)) $crate::io::print(alloc::format!($($args),*))
}; };
} }
#[macro_export] #[macro_export]

View File

@@ -4,7 +4,7 @@
//! scheduler, and logging), and starts initial processes. //! scheduler, and logging), and starts initial processes.
#![no_std] #![no_std]
#![no_main] #![no_main]
#![allow(static_mut_refs)] // #![warn(clippy::pedantic)]
#![feature( #![feature(
riscv_ext_intrinsics, riscv_ext_intrinsics,
const_trait_impl, const_trait_impl,
@@ -20,11 +20,10 @@ use embedded_alloc::LlffHeap as Heap;
use log::info; use log::info;
use crate::{ use crate::{
fs::FILE_SYSTEM, fs::FAT32_FILE_SYSTEM,
io::init_log, io::init_log,
process::{create_process, create_process_from_file},
riscv::enable_supervisor_interrupt, riscv::enable_supervisor_interrupt,
scheduler::{idle, scheduler_init}, scheduler::{idle, SCHEDULER},
user::{proc2, test}, user::{proc2, test},
vga::{Color, Vga}, vga::{Color, Vga},
}; };
@@ -39,11 +38,13 @@ mod panic_handler;
mod process; mod process;
mod riscv; mod riscv;
mod scheduler; mod scheduler;
mod sync;
mod syscall; mod syscall;
mod time; mod time;
mod uart; mod uart;
mod user; mod user;
mod vga; mod vga;
mod virtual_fs;
pub const HEAP_SIZE: usize = 1024 * 1024 * 32; // 32Mo RAM pub const HEAP_SIZE: usize = 1024 * 1024 * 32; // 32Mo RAM
#[global_allocator] #[global_allocator]
@@ -58,17 +59,19 @@ pub extern "C" fn supervisor_mode_entry() {
embedded_alloc::init!(HEAP, HEAP_SIZE); embedded_alloc::init!(HEAP, HEAP_SIZE);
init_log().unwrap(); init_log().unwrap();
Vga::init(); Vga::init();
FILE_SYSTEM.init(); FAT32_FILE_SYSTEM.init();
scheduler_init(); SCHEDULER.lock().init();
} }
info!("Hello World !"); info!("Hello World !");
unsafe { Vga::draw_string(10, 10, "Hello World !", Color::WHITE, Color::BLACK) }; unsafe { Vga::draw_string(10, 10, "Hello World !", Color::WHITE, Color::BLACK) };
create_process(Box::new(test), "proc1"); SCHEDULER.lock().create_process(Box::new(test), "proc1");
create_process(Box::new(proc2), "proc2"); 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(); enable_supervisor_interrupt();
idle(); idle();

View File

@@ -16,10 +16,11 @@ use hashbrown::HashMap;
use shared::syscall::exit; use shared::syscall::exit;
use crate::{ use crate::{
fs::FILE_SYSTEM, fs::FAT32_FILE_SYSTEM,
println, println,
scheduler::{scheduler_without_ret, ACTIVE_PID, PROCESS_COUNT, PROCESS_TABLE}, scheduler::{Scheduler, SCHEDULER},
time::elapsed_time_since_startup, time::elapsed_time_since_startup,
virtual_fs::VirtualNode,
}; };
/// Size of the stack allocated to each process (in 64-bit words). /// Size of the stack allocated to each process (in 64-bit words).
@@ -83,7 +84,7 @@ pub struct Process {
/// Current state of the process. /// Current state of the process.
pub state: ProcessState, pub state: ProcessState,
/// Optional entry point for the process code. /// Optional entry point for the process code.
pub entry: Option<Box<dyn Fn()>>, pub entry: Option<Box<dyn Fn() + Send>>,
/// Wake time for sleeping processes. /// Wake time for sleeping processes.
pub wake_time: Duration, pub wake_time: Duration,
/// Saved execution context. /// Saved execution context.
@@ -91,7 +92,37 @@ pub struct Process {
/// Process stack. /// Process stack.
pub stack: [u64; STACK_SIZE], pub stack: [u64; STACK_SIZE],
/// File descriptor table. /// File descriptor table.
pub fd_table: HashMap<u64, Path<'static>> pub fd_table: HashMap<u64, Box<dyn VirtualNode + Send>>,
/// 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 { impl core::fmt::Debug for Process {
@@ -107,187 +138,185 @@ impl core::fmt::Debug for Process {
} }
} }
/// Creates a process from a binary file. impl Scheduler {
/// /// Creates a process from a binary file.
/// # Arguments ///
/// /// # Arguments
/// * `path` - Path to the executable binary file. ///
/// /// * `path` - Path to the executable binary file.
/// # Returns ///
/// /// # Returns
/// Returns the PID of the created process, or -1 on failure. ///
/// /// Returns the PID of the created process, or -1 on failure.
/// # Safety ///
/// /// # Safety
/// This function uses `unsafe` to transmute the file content into an ///
/// executable function. The binary must be in the correct format and /// This function uses `unsafe` to transmute the file content into an
/// conform to the expected ABI. /// executable function. The binary must be in the correct format and
/// Create a process from an executable binary located on the filesystem. /// 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 /// Attempts to open `path`, load its contents into memory and create a new
/// created process, or -1 on failure. /// kernel process that will execute the loaded binary. Returns the PID of the
pub fn create_process_from_file<'a, T: Into<Path<'a>>>(path: T) -> i64 { /// created process, or -1 on failure.
let path = path.into(); pub fn create_process_from_file<'a, T: Into<Path<'a>>>(&mut self, path: T) -> i64 {
let name = path.as_str(); let path = path.into();
let name = path.as_str();
// Open and read the binary file // Open and read the binary file
let mut bin = FILE_SYSTEM.open_file(path).unwrap(); let mut bin = FAT32_FILE_SYSTEM.open_file(path).unwrap();
println!("Creating process"); println!("Creating process");
let mut content: Vec<u8> = Vec::new(); let mut content: Vec<u8> = Vec::new();
bin.read_to_end(&mut content).unwrap(); bin.read_to_end(&mut content).unwrap();
println!( println!(
"Loading binary at address: {:x?}, length: {}", "Loading binary at address: {:x?}, length: {}",
content.as_ptr(), content.as_ptr(),
content.len() content.len()
); );
// If ELF, use goblin to load PT_LOAD segments and apply relocations // If ELF, use goblin to load PT_LOAD segments and apply relocations
if let Ok(gelf) = goblin::elf::Elf::parse(&content) { if let Ok(gelf) = goblin::elf::Elf::parse(&content) {
println!("Parsed"); println!("Parsed");
// Determine memory bounds from program headers // Determine memory bounds from program headers
let mut min_vaddr = u64::MAX; let mut min_vaddr = u64::MAX;
let mut max_vaddr = 0u64; 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
for ph in gelf.program_headers.iter() { for ph in gelf.program_headers.iter() {
if ph.p_type == goblin::elf::program_header::PT_LOAD { if ph.p_type == goblin::elf::program_header::PT_LOAD && ph.p_memsz > 0 {
let dst = unsafe { base.add((ph.p_vaddr - min_vaddr) as usize) }; min_vaddr = core::cmp::min(min_vaddr, ph.p_vaddr);
let src_off = ph.p_offset as usize; max_vaddr = core::cmp::max(max_vaddr, ph.p_vaddr + ph.p_memsz);
let copy_len = ph.p_filesz as usize; }
if copy_len > 0 { }
unsafe {
core::ptr::copy_nonoverlapping( if min_vaddr != u64::MAX {
content.as_ptr().add(src_off), let size = (max_vaddr - min_vaddr) as usize;
dst, use alloc::alloc::{alloc_zeroed, Layout};
copy_len, 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 {
if ph.p_memsz as usize > copy_len { unsafe {
unsafe { core::slice::from_raw_parts_mut(
core::slice::from_raw_parts_mut( dst.add(copy_len),
dst.add(copy_len), ph.p_memsz as usize - copy_len,
ph.p_memsz as usize - copy_len, )
) .fill(0)
.fill(0) }
} }
} }
} }
} println!("Copied");
println!("Copied");
// Apply relocations using our parser (handles RELA entries) // Apply relocations using our parser (handles RELA entries)
for rela in gelf.dynrelas.iter() { for rela in gelf.dynrelas.iter() {
let r_type = rela.r_type; let r_type = rela.r_type;
match r_type { match r_type {
x if x == R_RISCV_RELATIVE => { x if x == R_RISCV_RELATIVE => {
let where_off = (rela.r_offset - min_vaddr) as usize; let where_off = (rela.r_offset - min_vaddr) as usize;
let where_ptr = unsafe { base.add(where_off) } as *mut u64; let where_ptr = unsafe { base.add(where_off) } as *mut u64;
let val = (base as u64).wrapping_add(rela.r_addend.unwrap() as u64); let val = (base as u64).wrapping_add(rela.r_addend.unwrap() as u64);
unsafe { core::ptr::write_unaligned(where_ptr, val) }; unsafe { core::ptr::write_unaligned(where_ptr, val) };
}
_ => {}
} }
_ => {}
} }
} println!("Relocated");
println!("Relocated");
// Entry point // Entry point
let entry_va = gelf.entry; let entry_va = gelf.entry;
let entry_addr = unsafe { base.add((entry_va - min_vaddr) as usize) } as *const u8; let entry_addr = unsafe { base.add((entry_va - min_vaddr) as usize) } as *const u8;
let entry_fn = let entry_fn =
unsafe { core::mem::transmute::<*const u8, extern "C" fn()>(entry_addr) }; unsafe { core::mem::transmute::<*const u8, extern "C" fn()>(entry_addr) };
let wrapper = Box::new(move || { let wrapper = Box::new(move || {
entry_fn(); entry_fn();
}); });
println!("Program loaded at : {:x?}", entry_addr); println!("Program loaded at : {:x?}", entry_addr);
return create_process(wrapper, name); 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<T: Into<String>, F: Fn() + 'static + Send>(
&mut self,
code: Box<F>,
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<T: Into<String>, F: Fn() + 'static>(code: Box<F>, 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. /// Entry point to launch a new process.
/// ///
/// This function is automatically called during the first scheduling /// This function is automatically called during the first scheduling
@@ -334,12 +363,11 @@ extern "C" fn process_launcher(code: *const Box<dyn Fn()>) {
/// to select the next runnable process. This function does not return. /// to select the next runnable process. This function does not return.
pub fn exit_process(interrupt_context: &mut *mut ExecutionContext) { pub fn exit_process(interrupt_context: &mut *mut ExecutionContext) {
// SAFETY: ACTIVE_PID is maintained by the scheduler and is always valid. // SAFETY: ACTIVE_PID is maintained by the scheduler and is always valid.
unsafe { let mut scheduler = SCHEDULER.lock();
PROCESS_TABLE[ACTIVE_PID].state = ProcessState::Dead; let active_pid = scheduler.active_pid;
} scheduler.process_table.remove(&active_pid).unwrap();
// Transfer control to the scheduler (does not return) // 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. /// 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. /// reactivate the process when the wake time is reached.
pub fn sleep(duration: Duration, interrupt_context: &mut *mut ExecutionContext) { pub fn sleep(duration: Duration, interrupt_context: &mut *mut ExecutionContext) {
// SAFETY: ACTIVE_PID is maintained by the scheduler and is always valid. // SAFETY: ACTIVE_PID is maintained by the scheduler and is always valid.
unsafe { let mut scheduler = SCHEDULER.lock();
let process = &mut PROCESS_TABLE[ACTIVE_PID]; let process = scheduler.get_current_process();
process.wake_time = elapsed_time_since_startup() + duration; process.wake_time = elapsed_time_since_startup() + duration;
process.state = ProcessState::Asleep; process.state = ProcessState::Asleep;
}
// Transfer control to the scheduler (does not return) // Transfer control to the scheduler (does not return)
scheduler_without_ret(interrupt_context) scheduler.schedule(interrupt_context)
} }

View File

@@ -3,50 +3,29 @@
//! //!
//! This module exposes the global process table, the scheduler initialization //! This module exposes the global process table, the scheduler initialization
//! and a simple round-robin scheduler used by the kernel. //! 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 alloc::{boxed::Box, collections::BTreeMap};
use hashbrown::HashMap;
use log::info; use log::info;
use crate::{ use crate::{
process::{create_process, ExecutionContext, Process, ProcessState}, process::{ExecutionContext, Process, ProcessState},
sync::Mutex,
time, time,
}; };
/// Maximum number of simultaneous processes supported by the kernel. #[derive(Debug)]
pub const PROCESS_COUNT: usize = 16; pub struct Scheduler {
pub next_pid: u64,
pub active_pid: u64,
pub process_table: BTreeMap<u64, Box<Process>>,
}
/// Currently active PID. pub static SCHEDULER: Mutex<LazyCell<Scheduler>> = Mutex::new(LazyCell::new(|| Scheduler {
/// next_pid: 0,
/// Updated by the scheduler when switching contexts. active_pid: 0,
pub static mut ACTIVE_PID: usize = 0; process_table: BTreeMap::new(),
/// 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()
})
});
/// Idle loop executed when there is no runnable process. /// Idle loop executed when there is no runnable process.
/// ///
@@ -61,53 +40,60 @@ pub fn idle() {
} }
} }
/// Initialize the scheduler and create the idle process. 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. /// Marks all process slots as `Dead` then creates the idle process and sets
pub fn scheduler_init() { /// it as the active process.
info!("scheduler init"); pub fn init(&mut self) {
for pid in 0..PROCESS_COUNT { 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 { 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)))
}
};
} }
} }
pub fn get_current_process(&mut self) -> &mut Process {
create_process(Box::new(idle), "idle"); self.process_table.get_mut(&self.active_pid).unwrap()
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
} }
} }

114
src/sync.rs Normal file
View File

@@ -0,0 +1,114 @@
use core::{
arch::riscv64::wfi,
cell::UnsafeCell,
ops::{Deref, DerefMut},
sync::atomic::{AtomicBool, Ordering},
};
#[derive(Debug)]
pub struct Mutex<T> {
locked: AtomicBool,
value: UnsafeCell<T>,
}
impl<T> Mutex<T> {
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<T: Send> Send for Mutex<T> {}
unsafe impl<T: Send> Sync for Mutex<T> {}
pub struct MutexGuard<'a, T> {
mutex: &'a Mutex<T>,
}
impl<T> 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<T, F> {
// value: ManuallyDrop<T>,
// f: ManuallyDrop<F>,
// }
// pub struct LazyLock<T, F = fn() -> T> {
// init: UnsafeCell<bool>,
// data: UnsafeCell<Data<T, F>>,
// }
// impl<T, F: Fn() -> T> LazyLock<T, F> {
// 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, F: Fn() -> T> Deref for LazyLock<T, F> {
// type Target = T;
// fn deref(&self) -> &Self::Target {
// self.force()
// }
// }
// impl<T, F: Fn() -> T> DerefMut for LazyLock<T, F> {
// fn deref_mut(&mut self) -> &mut Self::Target {
// self.force_mut()
// }
// }
// unsafe impl<T: Sync + Send, F: Send> Sync for LazyLock<T, F> {}

View File

@@ -1,8 +1,12 @@
use core::alloc::Layout; use core::alloc::Layout;
use alloc::boxed::Box;
use bffs::{error::Error, path::Path}; 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 { pub unsafe fn alloc(layout: Layout) -> *mut u8 {
unsafe { alloc::alloc::alloc(layout) } 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<'a>>>( pub fn open<'a, P: Into<Path<'a>>>(
path: P, path: P,
in_kernel: bool, in_kernel: bool,
) -> Result<u64, Error<<Disk as bffs::io::IoBase>::Error>> { ) -> Result<Box<dyn VirtualNode + Send>, Error<<Disk as bffs::io::IoBase>::Error>> {
let path = path.into(); let path = path.into();
let file = match path.split_path() { let file = match path.split_path() {
("dev", path) => { ("dev", path) => {
unimplemented!() todo!()
} }
_ => FILE_SYSTEM.open_file(path)?, _ => FAT32_FILE_SYSTEM.open_file(path)?,
}; };
Ok(42) todo!()
} }

View File

@@ -1,7 +1,10 @@
//! Time and timer interrupt management for the kernel. //! Time and timer interrupt management for the kernel.
use core::time::Duration;
use alloc::format; use alloc::format;
use core::{
sync::atomic::{AtomicU64, Ordering},
time::Duration,
};
use crate::{ use crate::{
set_csr, set_csr,
@@ -21,16 +24,16 @@ const CLINT_TIMER: *const u64 = 0x0200_bff8 as *const u64;
/// The hardware timer frequency (Hz). /// The hardware timer frequency (Hz).
const TIMER_FREQUENCY: u64 = 10_000_000; // 10 MHz const TIMER_FREQUENCY: u64 = 10_000_000; // 10 MHz
/// The frequency at which timer interrupts should occur (Hz). /// 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. /// 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. /// Initializes the timer interrupt system and records the kernel start time.
/// ///
/// This should be called once during kernel initialization. /// This should be called once during kernel initialization.
pub fn setup_timer_interrupt() { pub fn setup_timer_interrupt() {
unsafe { START_TIME = Instant::now(); } START_TIME.store(Instant::now().0, Ordering::Relaxed);
set_csr!(sie, IRQ_S_TIMER); set_csr!(sie, IRQ_S_TIMER);
setup_next_timer_interrupt(); setup_next_timer_interrupt();
} }
@@ -69,7 +72,8 @@ pub fn timer_interrupt() {
/// Returns the duration since the kernel was started. /// Returns the duration since the kernel was started.
pub fn elapsed_time_since_startup() -> Duration { 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. /// Represents a point in time, based on the hardware timer.

View File

@@ -4,16 +4,9 @@
//! and the scheduler. //! and the scheduler.
use core::time::Duration; use core::time::Duration;
use shared::syscall::{sleep, write_int_temp, write_string_temp}; use shared::syscall::{sleep, 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"));
pub fn test() { 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 { loop {
write_string_temp("test"); write_string_temp("test");
sleep(Duration::new(2, 0)); sleep(Duration::new(2, 0));

21
src/virtual_fs.rs Normal file
View File

@@ -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<usize, ()>;
}
pub trait VirtualFileSystem {
fn open<'a, P: Into<Path<'a>>>(path: P) -> Result<Box<dyn VirtualNode + Send>, ()>;
}
pub struct MainFileSystem {
mounts: HashMap<PathBuf>
}
impl VirtualFileSystem for MainFileSystem {
fn open<'a, P: Into<Path<'a>>>(path: P) -> Result<Box<dyn VirtualNode + Send>, ()> {
todo!()
}
}