diff --git a/crates/domain/src/error.rs b/crates/domain/src/error.rs index 134cd86..005fc5c 100644 --- a/crates/domain/src/error.rs +++ b/crates/domain/src/error.rs @@ -1,4 +1,8 @@ -use types::{bounded::CapSlot, id::ProcessId, permission::Permission}; +use types::{ + bounded::CapSlot, + id::{ProcessId, ThreadId}, + permission::Permission, +}; #[derive(Debug)] pub enum KernelError { @@ -10,4 +14,5 @@ pub enum KernelError { WouldBlock, TooManyThreadsInProcess, TooManyProcesses, + InvalidStateTransition(ThreadId), } diff --git a/crates/domain/src/thread/mod.rs b/crates/domain/src/thread/mod.rs new file mode 100644 index 0000000..38ebc97 --- /dev/null +++ b/crates/domain/src/thread/mod.rs @@ -0,0 +1,143 @@ +use types::{ + bounded::{CapSlot, Priority}, + id::{CoreId, ProcessId, ThreadId}, + time::TimeSlice, +}; + +use crate::error::KernelError; + +#[cfg(test)] +#[path = "tests.rs"] +mod tests; + +pub struct Thread { + id: ThreadId, + process: ProcessId, + state: ThreadState, + saved_context: SavedContext, + priority: Priority, + time_slice: TimeSlice, + core_affinity: Option, +} + +pub enum ThreadState { + Ready, + Running(CoreId), + Blocked(BlockReason), + Dead, +} + +pub struct SavedContext { + registers: [usize; 32], +} + +impl SavedContext { + pub fn empty() -> Self { + Self { registers: [0; 32] } + } +} + +pub enum BlockReason { + Sending(CapSlot), + Receiving(CapSlot), + WaitingForInterrupt(CapSlot), + WaitingAny, +} + +impl Thread { + pub fn new( + id: ThreadId, + process: ProcessId, + priority: Priority, + time_slice: TimeSlice, + core_affinity: Option, + ) -> Self { + Self { + id, + process, + state: ThreadState::Ready, + saved_context: SavedContext::empty(), + priority, + time_slice, + core_affinity, + } + } + + pub fn id(&self) -> ThreadId { + self.id + } + + pub fn process(&self) -> ProcessId { + self.process + } + + pub fn state(&self) -> &ThreadState { + &self.state + } + + pub fn priority(&self) -> Priority { + self.priority + } + + pub fn time_slice(&self) -> &TimeSlice { + &self.time_slice + } + + pub fn time_slice_mut(&mut self) -> &mut TimeSlice { + &mut self.time_slice + } + + pub fn core_affinity(&self) -> Option { + self.core_affinity + } + + pub fn set_running(&mut self, core: CoreId) -> Result<(), KernelError> { + match self.state { + ThreadState::Ready => { + self.state = ThreadState::Running(core); + Ok(()) + } + _ => Err(KernelError::InvalidStateTransition(self.id)), + } + } + + pub fn block(&mut self, reason: BlockReason) -> Result<(), KernelError> { + match self.state { + ThreadState::Running(_) => { + self.state = ThreadState::Blocked(reason); + Ok(()) + } + _ => Err(KernelError::InvalidStateTransition(self.id)), + } + } + + pub fn unblock(&mut self) -> Result<(), KernelError> { + match self.state { + ThreadState::Blocked(_) => { + self.state = ThreadState::Ready; + Ok(()) + } + _ => Err(KernelError::InvalidStateTransition(self.id)), + } + } + + pub fn preempt(&mut self) -> Result<(), KernelError> { + match self.state { + ThreadState::Running(_) => { + self.state = ThreadState::Ready; + Ok(()) + } + _ => Err(KernelError::InvalidStateTransition(self.id)), + } + } + + pub fn exit(&mut self) -> Result<(), KernelError> { + match self.state { + ThreadState::Running(_) => { + self.state = ThreadState::Dead; + Ok(()) + } + _ => Err(KernelError::InvalidStateTransition(self.id)), + } + } +} diff --git a/crates/domain/src/thread/tests.rs b/crates/domain/src/thread/tests.rs new file mode 100644 index 0000000..085da61 --- /dev/null +++ b/crates/domain/src/thread/tests.rs @@ -0,0 +1,158 @@ +use super::*; +use types::bounded::Priority; +use types::id::{CoreId, ProcessId, ThreadId}; +use types::time::TimeSlice; + +fn thread(id: u16) -> ThreadId { + ThreadId::from_raw(id) +} + +fn process(id: u16) -> ProcessId { + ProcessId::from_raw(id) +} + +fn core(id: u8) -> CoreId { + CoreId::from_raw(id) +} + +fn prio(value: u8) -> Priority { + Priority::new(value).unwrap() +} + +fn time_slice() -> TimeSlice { + TimeSlice::new(10) +} + +fn test_thread(id: u16, proc_id: u16) -> Thread { + Thread::new(thread(id), process(proc_id), prio(10), time_slice(), None) +} + +// --- SavedContext tests --- + +#[test] +fn saved_context_empty_creates_zeroed() { + let ctx = SavedContext::empty(); + let _ = ctx; +} + +// --- Thread construction tests --- + +#[test] +fn new_thread_is_ready() { + let t = test_thread(0, 0); + assert!(matches!(t.state(), ThreadState::Ready)); +} + +#[test] +fn accessors_return_construction_values() { + let t = Thread::new(thread(5), process(3), prio(20), time_slice(), Some(core(1))); + assert_eq!(t.id(), thread(5)); + assert_eq!(t.process(), process(3)); + assert_eq!(t.priority(), prio(20)); + assert_eq!(t.core_affinity(), Some(core(1))); +} + +// --- Valid state transitions --- + +#[test] +fn ready_to_running() { + let mut t = test_thread(0, 0); + t.set_running(core(0)).unwrap(); + assert!(matches!(t.state(), ThreadState::Running(c) if *c == core(0))); +} + +#[test] +fn running_to_blocked() { + let mut t = test_thread(0, 0); + t.set_running(core(0)).unwrap(); + t.block(BlockReason::WaitingAny).unwrap(); + assert!(matches!(t.state(), ThreadState::Blocked(BlockReason::WaitingAny))); +} + +#[test] +fn blocked_to_ready() { + let mut t = test_thread(0, 0); + t.set_running(core(0)).unwrap(); + t.block(BlockReason::WaitingAny).unwrap(); + t.unblock().unwrap(); + assert!(matches!(t.state(), ThreadState::Ready)); +} + +#[test] +fn running_to_ready_via_preempt() { + let mut t = test_thread(0, 0); + t.set_running(core(0)).unwrap(); + t.preempt().unwrap(); + assert!(matches!(t.state(), ThreadState::Ready)); +} + +#[test] +fn running_to_dead() { + let mut t = test_thread(0, 0); + t.set_running(core(0)).unwrap(); + t.exit().unwrap(); + assert!(matches!(t.state(), ThreadState::Dead)); +} + +// --- Invalid state transitions --- + +#[test] +fn dead_cannot_transition() { + let mut t = test_thread(0, 0); + t.set_running(core(0)).unwrap(); + t.exit().unwrap(); + assert!(t.set_running(core(0)).is_err()); + assert!(t.block(BlockReason::WaitingAny).is_err()); + assert!(t.unblock().is_err()); + assert!(t.preempt().is_err()); + assert!(t.exit().is_err()); +} + +#[test] +fn ready_cannot_block() { + let mut t = test_thread(0, 0); + assert!(t.block(BlockReason::WaitingAny).is_err()); +} + +#[test] +fn blocked_cannot_run_directly() { + let mut t = test_thread(0, 0); + t.set_running(core(0)).unwrap(); + t.block(BlockReason::WaitingAny).unwrap(); + assert!(t.set_running(core(0)).is_err()); +} + +#[test] +fn ready_cannot_preempt() { + let mut t = test_thread(0, 0); + assert!(t.preempt().is_err()); +} + +#[test] +fn ready_cannot_exit() { + let mut t = test_thread(0, 0); + assert!(t.exit().is_err()); +} + +#[test] +fn blocked_cannot_preempt() { + let mut t = test_thread(0, 0); + t.set_running(core(0)).unwrap(); + t.block(BlockReason::WaitingAny).unwrap(); + assert!(t.preempt().is_err()); +} + +// --- Time slice --- + +#[test] +fn time_slice_accessible() { + let t = test_thread(0, 0); + let _ = t.time_slice(); +} + +#[test] +fn time_slice_mut_ticks() { + let mut t = test_thread(0, 0); + let expired = t.time_slice_mut().tick(); + assert!(!expired); +}