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"
[unstable]
json-target-spec = true
build-std = ["core", "compiler_builtins", "alloc"]
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> {
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 {}
}

View File

@@ -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

View File

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

View File

@@ -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);

View File

@@ -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<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},
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)]

View File

@@ -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]

View File

@@ -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();

View File

@@ -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<Box<dyn Fn()>>,
pub entry: Option<Box<dyn Fn() + Send>>,
/// 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<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 {
@@ -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<'a>>>(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<Path<'a>>>(&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<u8> = 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<u8> = 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<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.
///
/// 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.
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)
}

View File

@@ -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<u64, Box<Process>>,
}
/// 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<LazyCell<Scheduler>> = 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()
}
}

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 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<'a>>>(
path: P,
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 file = match path.split_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.
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.

View File

@@ -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));

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!()
}
}