feat(domain): add Thread constructor, accessors, and state machine transitions
This commit is contained in:
@@ -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),
|
||||
}
|
||||
|
||||
143
crates/domain/src/thread/mod.rs
Normal file
143
crates/domain/src/thread/mod.rs
Normal file
@@ -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<CoreId>,
|
||||
}
|
||||
|
||||
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<CoreId>,
|
||||
) -> 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<CoreId> {
|
||||
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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
158
crates/domain/src/thread/tests.rs
Normal file
158
crates/domain/src/thread/tests.rs
Normal file
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user