diff --git a/crates/domain/src/error.rs b/crates/domain/src/error.rs index 005fc5c..f998b31 100644 --- a/crates/domain/src/error.rs +++ b/crates/domain/src/error.rs @@ -15,4 +15,6 @@ pub enum KernelError { TooManyThreadsInProcess, TooManyProcesses, InvalidStateTransition(ThreadId), + InvalidThread(ThreadId), + TooManyThreads, } diff --git a/crates/domain/src/thread/mod.rs b/crates/domain/src/thread/mod.rs index 38ebc97..77a125e 100644 --- a/crates/domain/src/thread/mod.rs +++ b/crates/domain/src/thread/mod.rs @@ -141,3 +141,66 @@ impl Thread { } } } + +pub const MAX_THREADS: usize = 256; + +pub struct ThreadTable { + threads: [Option; MAX_THREADS], +} + +impl ThreadTable { + pub fn new() -> Self { + Self { + threads: [const { None }; MAX_THREADS], + } + } + + pub fn spawn( + &mut self, + process: ProcessId, + priority: Priority, + time_slice: TimeSlice, + core_affinity: Option, + ) -> Result { + let idx = self + .threads + .iter() + .position(|s| s.is_none()) + .ok_or(KernelError::TooManyThreads)?; + + let id = ThreadId::from_raw(idx as u16); + self.threads[idx] = Some(Thread::new(id, process, priority, time_slice, core_affinity)); + Ok(id) + } + + pub fn get(&self, id: ThreadId) -> Result<&Thread, KernelError> { + let idx = id.raw() as usize; + self.threads + .get(idx) + .and_then(|s| s.as_ref()) + .ok_or(KernelError::InvalidThread(id)) + } + + pub fn get_mut(&mut self, id: ThreadId) -> Result<&mut Thread, KernelError> { + let idx = id.raw() as usize; + self.threads + .get_mut(idx) + .and_then(|s| s.as_mut()) + .ok_or(KernelError::InvalidThread(id)) + } + + pub fn destroy(&mut self, id: ThreadId) -> Result { + let idx = id.raw() as usize; + self.threads + .get_mut(idx) + .and_then(|s| s.take()) + .ok_or(KernelError::InvalidThread(id)) + } + + pub fn threads_for_process(&self, process: ProcessId) -> impl Iterator { + self.threads + .iter() + .filter_map(|s| s.as_ref()) + .filter(move |t| t.process() == process) + } +} diff --git a/crates/domain/src/thread/tests.rs b/crates/domain/src/thread/tests.rs index 085da61..9d23e6f 100644 --- a/crates/domain/src/thread/tests.rs +++ b/crates/domain/src/thread/tests.rs @@ -156,3 +156,58 @@ fn time_slice_mut_ticks() { let expired = t.time_slice_mut().tick(); assert!(!expired); } + +// --- ThreadTable tests --- + +#[test] +fn table_spawn_and_lookup() { + let mut table = ThreadTable::new(); + let id = table.spawn(process(0), prio(10), time_slice(), None).unwrap(); + let t = table.get(id).unwrap(); + assert_eq!(t.id(), id); + assert_eq!(t.process(), process(0)); +} + +#[test] +fn table_spawn_returns_unique_ids() { + let mut table = ThreadTable::new(); + let a = table.spawn(process(0), prio(10), time_slice(), None).unwrap(); + let b = table.spawn(process(0), prio(10), time_slice(), None).unwrap(); + assert_ne!(a, b); +} + +#[test] +fn table_destroy_then_get_fails() { + let mut table = ThreadTable::new(); + let id = table.spawn(process(0), prio(10), time_slice(), None).unwrap(); + table.destroy(id).unwrap(); + assert!(table.get(id).is_err()); +} + +#[test] +fn table_threads_for_process_filters() { + let mut table = ThreadTable::new(); + table.spawn(process(0), prio(10), time_slice(), None).unwrap(); + table.spawn(process(1), prio(10), time_slice(), None).unwrap(); + table.spawn(process(0), prio(5), time_slice(), None).unwrap(); + + let count = table.threads_for_process(process(0)).count(); + assert_eq!(count, 2); +} + +#[test] +fn table_get_mut_allows_state_transition() { + let mut table = ThreadTable::new(); + let id = table.spawn(process(0), prio(10), time_slice(), None).unwrap(); + table.get_mut(id).unwrap().set_running(core(0)).unwrap(); + assert!(matches!(table.get(id).unwrap().state(), ThreadState::Running(_))); +} + +#[test] +fn table_destroy_reuses_slot() { + let mut table = ThreadTable::new(); + let id = table.spawn(process(0), prio(10), time_slice(), None).unwrap(); + table.destroy(id).unwrap(); + let new_id = table.spawn(process(0), prio(10), time_slice(), None).unwrap(); + assert_eq!(id, new_id); +}