Sync computers
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
target = "riscv64.json"
|
||||
|
||||
[unstable]
|
||||
json-target-spec = true
|
||||
build-std = ["core", "compiler_builtins", "alloc"]
|
||||
build-std-features = ["compiler-builtins-mem"]
|
||||
|
||||
|
||||
@@ -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 {}
|
||||
}
|
||||
|
||||
2
justfile
2
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
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
components = ["rust-src"]
|
||||
|
||||
@@ -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);
|
||||
|
||||
15
src/fs.rs
15
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<HashMap<u64, Path<'static>>>);
|
||||
|
||||
unsafe impl Sync for KernelFDTable {}
|
||||
|
||||
pub static KERNEL_FILE_DESCRIPTOR_TABLE: KernelFDTable = KernelFDTable(LazyCell::new(HashMap::new));
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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]
|
||||
|
||||
21
src/main.rs
21
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();
|
||||
|
||||
179
src/process.rs
179
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<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,32 +138,33 @@ 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 {
|
||||
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();
|
||||
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();
|
||||
@@ -214,59 +246,54 @@ pub fn create_process_from_file<'a, T: Into<Path<'a>>>(path: T) -> i64 {
|
||||
entry_fn();
|
||||
});
|
||||
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 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
|
||||
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 {
|
||||
let process = &mut PROCESS_TABLE[next_pid];
|
||||
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 = next_pid as i64;
|
||||
process.pid = self.next_pid as i64;
|
||||
process.name = name.into();
|
||||
process.state = ProcessState::Activable;
|
||||
process.entry = Some(code);
|
||||
@@ -283,11 +310,13 @@ pub fn create_process<T: Into<String>, F: Fn() + 'static>(code: Box<F>, name: T)
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
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)
|
||||
}
|
||||
|
||||
130
src/scheduler.rs
130
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<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() {
|
||||
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");
|
||||
for pid in 0..PROCESS_COUNT {
|
||||
unsafe {
|
||||
PROCESS_TABLE[pid].state = ProcessState::Dead;
|
||||
}
|
||||
self.create_process(Box::new(idle), "idle");
|
||||
self.process_table.get_mut(&0).unwrap().state = ProcessState::Active;
|
||||
}
|
||||
|
||||
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) {
|
||||
/// 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 {
|
||||
let prev_pid = ACTIVE_PID;
|
||||
PROCESS_TABLE[prev_pid].ctx = **interrupt_state;
|
||||
let prev_pid = self.active_pid;
|
||||
if let Some(previous_process) = self.process_table.get_mut(&prev_pid) {
|
||||
previous_process.ctx = **interrupt_state;
|
||||
|
||||
if PROCESS_TABLE[prev_pid].state == ProcessState::Active {
|
||||
PROCESS_TABLE[prev_pid].state = ProcessState::Activable;
|
||||
if previous_process.state == ProcessState::Active {
|
||||
previous_process.state = ProcessState::Activable;
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
if PROCESS_TABLE[ACTIVE_PID].state == ProcessState::Asleep
|
||||
&& time::elapsed_time_since_startup() > PROCESS_TABLE[ACTIVE_PID].wake_time
|
||||
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
|
||||
{
|
||||
PROCESS_TABLE[ACTIVE_PID].state = ProcessState::Activable;
|
||||
current_process.state = ProcessState::Activable;
|
||||
}
|
||||
ACTIVE_PID = (ACTIVE_PID + 1) % PROCESS_COUNT;
|
||||
if PROCESS_TABLE[ACTIVE_PID].state == ProcessState::Activable {
|
||||
break;
|
||||
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)))
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
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
114
src/sync.rs
Normal 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> {}
|
||||
@@ -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!()
|
||||
}
|
||||
|
||||
12
src/time.rs
12
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,
|
||||
@@ -24,13 +27,13 @@ const TIMER_FREQUENCY: u64 = 10_000_000; // 10 MHz
|
||||
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.
|
||||
|
||||
@@ -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
21
src/virtual_fs.rs
Normal 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!()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user