Implement domain and endpoint modules with capability management, message passing, and scheduling features
This commit is contained in:
@@ -4,3 +4,4 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
types = { workspace = true }
|
||||
|
||||
33
crates/domain/src/capability/authority.rs
Normal file
33
crates/domain/src/capability/authority.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use types::{
|
||||
bounded::CapSlot,
|
||||
id::ProcessId,
|
||||
permission::{Permission, PermissionSet},
|
||||
};
|
||||
|
||||
use crate::{capability::Capability, error::KernelError, resource::Resource};
|
||||
|
||||
pub trait CapabilityAuthority {
|
||||
fn grant(
|
||||
&mut self,
|
||||
to: ProcessId,
|
||||
resource: Resource,
|
||||
permissions: PermissionSet,
|
||||
) -> Result<CapSlot, KernelError>;
|
||||
|
||||
fn revoke(&mut self, owner: ProcessId, slot: CapSlot) -> Result<(), KernelError>;
|
||||
|
||||
fn delegate(
|
||||
&mut self,
|
||||
from: ProcessId,
|
||||
slot: CapSlot,
|
||||
to: ProcessId,
|
||||
restrict: PermissionSet,
|
||||
) -> Result<CapSlot, KernelError>;
|
||||
|
||||
fn check(
|
||||
&self,
|
||||
caller: ProcessId,
|
||||
slot: CapSlot,
|
||||
needed: Permission,
|
||||
) -> Result<&Capability, KernelError>;
|
||||
}
|
||||
82
crates/domain/src/capability/mod.rs
Normal file
82
crates/domain/src/capability/mod.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use types::{bounded::CapSlot, generation::Generation, permission::PermissionSet};
|
||||
|
||||
pub mod authority;
|
||||
|
||||
use crate::{error::KernelError, resource::Resource};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests.rs"]
|
||||
mod tests;
|
||||
|
||||
const MAX_CAPS_PER_PROCESS: usize = 16;
|
||||
|
||||
pub struct Capability {
|
||||
resource: Resource,
|
||||
permissions: PermissionSet,
|
||||
generation: Generation,
|
||||
}
|
||||
|
||||
impl Capability {
|
||||
pub(crate) fn new(
|
||||
resource: Resource,
|
||||
permissions: PermissionSet,
|
||||
generation: Generation,
|
||||
) -> Self {
|
||||
Self {
|
||||
resource,
|
||||
permissions,
|
||||
generation,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resource(&self) -> &Resource {
|
||||
&self.resource
|
||||
}
|
||||
|
||||
pub fn permissions(&self) -> &PermissionSet {
|
||||
&self.permissions
|
||||
}
|
||||
|
||||
pub fn generation(&self) -> &Generation {
|
||||
&self.generation
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CapabilityTable {
|
||||
slots: [Option<Capability>; MAX_CAPS_PER_PROCESS],
|
||||
}
|
||||
|
||||
impl CapabilityTable {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
slots: [const { None }; MAX_CAPS_PER_PROCESS],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, slot: CapSlot) -> Result<&Capability, KernelError> {
|
||||
let idx = slot.value() as usize;
|
||||
self.slots[idx]
|
||||
.as_ref()
|
||||
.ok_or(KernelError::InvalidSlot(slot))
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, cap: Capability) -> Result<CapSlot, KernelError> {
|
||||
let idx = self
|
||||
.slots
|
||||
.iter()
|
||||
.position(|s| s.is_none())
|
||||
.ok_or(KernelError::NoFreeSlot)?;
|
||||
|
||||
self.slots[idx] = Some(cap);
|
||||
CapSlot::new(idx as u8).ok_or(KernelError::NoFreeSlot)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, slot: CapSlot) -> Result<(), KernelError> {
|
||||
let idx = slot.value() as usize;
|
||||
if self.slots[idx].is_none() {
|
||||
return Err(KernelError::InvalidSlot(slot));
|
||||
}
|
||||
self.slots[idx] = None;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
152
crates/domain/src/capability/tests.rs
Normal file
152
crates/domain/src/capability/tests.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
use super::*;
|
||||
use types::generation::Generation;
|
||||
use types::id::{EndpointId, RegionId};
|
||||
use types::permission::{Permission, PermissionSet};
|
||||
|
||||
fn perms(permissions: &[Permission]) -> PermissionSet {
|
||||
permissions
|
||||
.iter()
|
||||
.fold(PermissionSet::empty(), |set, &p| set.with(p))
|
||||
}
|
||||
|
||||
fn endpoint_cap(id: u16, permissions: &[Permission]) -> Capability {
|
||||
Capability::new(
|
||||
Resource::Endpoint(EndpointId::from_raw(id)),
|
||||
perms(permissions),
|
||||
Generation::initial(),
|
||||
)
|
||||
}
|
||||
|
||||
fn memory_cap(id: u16, permissions: &[Permission]) -> Capability {
|
||||
Capability::new(
|
||||
Resource::Memory(RegionId::from_raw(id)),
|
||||
perms(permissions),
|
||||
Generation::initial(),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_table_is_empty() {
|
||||
let table = CapabilityTable::new();
|
||||
let slot = CapSlot::new(0).unwrap();
|
||||
assert!(table.get(slot).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_and_get() {
|
||||
let mut table = CapabilityTable::new();
|
||||
let slot = table.insert(endpoint_cap(1, &[Permission::Read])).unwrap();
|
||||
let retrieved = table.get(slot).unwrap();
|
||||
|
||||
assert!(retrieved.permissions().allows(Permission::Read));
|
||||
assert!(!retrieved.permissions().allows(Permission::Write));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_then_get_fails() {
|
||||
let mut table = CapabilityTable::new();
|
||||
let slot = table.insert(endpoint_cap(1, &[Permission::Read])).unwrap();
|
||||
table.remove(slot).unwrap();
|
||||
assert!(table.get(slot).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_invalid_slot_fails() {
|
||||
let mut table = CapabilityTable::new();
|
||||
let slot = CapSlot::new(5).unwrap();
|
||||
|
||||
assert!(table.remove(slot).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_reuses_removed_slot() {
|
||||
let mut table = CapabilityTable::new();
|
||||
let slot = table.insert(endpoint_cap(1, &[Permission::Read])).unwrap();
|
||||
table.remove(slot).unwrap();
|
||||
let new_slot = table.insert(endpoint_cap(2, &[Permission::Write])).unwrap();
|
||||
assert_eq!(slot, new_slot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_table_returns_no_free_slot() {
|
||||
let mut table = CapabilityTable::new();
|
||||
for i in 0..MAX_CAPS_PER_PROCESS {
|
||||
assert!(
|
||||
table
|
||||
.insert(endpoint_cap(i as u16, &[Permission::Read]))
|
||||
.is_ok()
|
||||
);
|
||||
}
|
||||
assert!(table.insert(endpoint_cap(999, &[])).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generation_mismatch_detected() {
|
||||
let gen_v1 = Generation::initial();
|
||||
let gen_v2 = gen_v1.next();
|
||||
|
||||
assert!(gen_v1.is_valid_against(&gen_v1));
|
||||
assert!(!gen_v1.is_valid_against(&gen_v2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restrict_to_never_escalates() {
|
||||
let full = perms(&[Permission::Read, Permission::Write, Permission::Execute]);
|
||||
let read_only = perms(&[Permission::Read]);
|
||||
|
||||
let restricted = full.restrict_to(&read_only);
|
||||
|
||||
assert!(restricted.allows(Permission::Read));
|
||||
assert!(!restricted.allows(Permission::Write));
|
||||
assert!(!restricted.allows(Permission::Execute));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restrict_to_cannot_add_permissions() {
|
||||
let read_only = perms(&[Permission::Read]);
|
||||
let wants_everything = perms(&[
|
||||
Permission::Read,
|
||||
Permission::Write,
|
||||
Permission::Execute,
|
||||
Permission::Delegate,
|
||||
Permission::Revoke,
|
||||
]);
|
||||
|
||||
let result = read_only.restrict_to(&wants_everything);
|
||||
|
||||
assert!(result.allows(Permission::Read));
|
||||
assert!(!result.allows(Permission::Write));
|
||||
assert!(!result.allows(Permission::Execute));
|
||||
assert!(!result.allows(Permission::Delegate));
|
||||
assert!(!result.allows(Permission::Revoke));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_subset_of() {
|
||||
let rw = perms(&[Permission::Read, Permission::Write]);
|
||||
let read_only = perms(&[Permission::Read]);
|
||||
|
||||
assert!(read_only.is_subset_of(&rw));
|
||||
assert!(!rw.is_subset_of(&read_only));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_permission_set_allows_nothing() {
|
||||
let empty = PermissionSet::empty();
|
||||
|
||||
assert!(!empty.allows(Permission::Read));
|
||||
assert!(!empty.allows(Permission::Write));
|
||||
assert!(!empty.allows(Permission::Execute));
|
||||
assert!(!empty.allows(Permission::Delegate));
|
||||
assert!(!empty.allows(Permission::Revoke));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn capability_preserves_resource_and_permissions() {
|
||||
let permissions = [Permission::Read, Permission::Write];
|
||||
let expected_permissions = perms(&permissions);
|
||||
let cap = memory_cap(42, &permissions);
|
||||
|
||||
assert_eq!(cap.permissions(), &expected_permissions);
|
||||
assert_eq!(cap.generation(), &Generation::initial());
|
||||
}
|
||||
78
crates/domain/src/endpoint/mod.rs
Normal file
78
crates/domain/src/endpoint/mod.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
use types::{
|
||||
bounded::CapSlot,
|
||||
generation::Generation,
|
||||
id::{EndpointId, ProcessId, ThreadId},
|
||||
};
|
||||
|
||||
use crate::{endpoint::wait_queue::WaitQueue, error::KernelError, message::Message};
|
||||
|
||||
pub mod wait_queue;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests.rs"]
|
||||
mod tests;
|
||||
|
||||
pub struct Endpoint {
|
||||
id: EndpointId,
|
||||
waiting_senders: WaitQueue,
|
||||
waiting_receivers: WaitQueue,
|
||||
generation: Generation,
|
||||
}
|
||||
|
||||
impl Endpoint {
|
||||
pub fn new(id: EndpointId) -> Self {
|
||||
Self {
|
||||
id,
|
||||
waiting_senders: WaitQueue::new(),
|
||||
waiting_receivers: WaitQueue::new(),
|
||||
generation: Generation::initial(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &EndpointId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub fn generation(&self) -> &Generation {
|
||||
&self.generation
|
||||
}
|
||||
|
||||
pub fn enqueue_sender(&mut self, thread: ThreadId) -> Option<()> {
|
||||
self.waiting_senders.push(thread)
|
||||
}
|
||||
|
||||
pub fn dequeue_sender(&mut self) -> Option<ThreadId> {
|
||||
self.waiting_senders.pop()
|
||||
}
|
||||
|
||||
pub fn enqueue_receiver(&mut self, thread: ThreadId) -> Option<()> {
|
||||
self.waiting_receivers.push(thread)
|
||||
}
|
||||
|
||||
pub fn dequeue_receiver(&mut self) -> Option<ThreadId> {
|
||||
self.waiting_receivers.pop()
|
||||
}
|
||||
|
||||
pub fn has_waiting_sender(&self) -> bool {
|
||||
!self.waiting_senders.is_empty()
|
||||
}
|
||||
|
||||
pub fn has_waiting_receiver(&self) -> bool {
|
||||
!self.waiting_receivers.is_empty()
|
||||
}
|
||||
|
||||
pub fn revoke(&mut self) {
|
||||
self.generation = self.generation.next();
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MessagePassing {
|
||||
fn send(
|
||||
&mut self,
|
||||
sender: ProcessId,
|
||||
endpoint: CapSlot,
|
||||
message: Message,
|
||||
) -> Result<(), KernelError>;
|
||||
|
||||
fn receive(&mut self, receiver: ProcessId, endpoint: CapSlot) -> Result<Message, KernelError>;
|
||||
}
|
||||
137
crates/domain/src/endpoint/tests.rs
Normal file
137
crates/domain/src/endpoint/tests.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
use crate::endpoint::wait_queue::MAX_WAITERS;
|
||||
|
||||
use super::*;
|
||||
use types::id::{EndpointId, ThreadId};
|
||||
|
||||
fn thread(id: u16) -> ThreadId {
|
||||
ThreadId::from_raw(id)
|
||||
}
|
||||
|
||||
fn test_endpoint() -> Endpoint {
|
||||
Endpoint::new(EndpointId::from_raw(1))
|
||||
}
|
||||
|
||||
// --- WaitQueue tests ---
|
||||
|
||||
#[test]
|
||||
fn empty_queue_pop_returns_none() {
|
||||
let mut queue = WaitQueue::new();
|
||||
assert!(queue.pop().is_none());
|
||||
assert!(queue.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_then_pop_returns_same_thread() {
|
||||
let mut queue = WaitQueue::new();
|
||||
queue.push(thread(1)).unwrap();
|
||||
assert_eq!(queue.pop(), Some(thread(1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pop_order_is_fifo() {
|
||||
let mut queue = WaitQueue::new();
|
||||
queue.push(thread(1)).unwrap();
|
||||
queue.push(thread(2)).unwrap();
|
||||
queue.push(thread(3)).unwrap();
|
||||
|
||||
assert_eq!(queue.pop(), Some(thread(1)));
|
||||
assert_eq!(queue.pop(), Some(thread(2)));
|
||||
assert_eq!(queue.pop(), Some(thread(3)));
|
||||
assert!(queue.pop().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_queue_rejects_push() {
|
||||
let mut queue = WaitQueue::new();
|
||||
for i in 0..MAX_WAITERS {
|
||||
assert!(queue.push(thread(i as u16)).is_some());
|
||||
}
|
||||
assert!(queue.push(thread(999)).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_empty_after_drain() {
|
||||
let mut queue = WaitQueue::new();
|
||||
queue.push(thread(1)).unwrap();
|
||||
queue.push(thread(2)).unwrap();
|
||||
queue.pop();
|
||||
queue.pop();
|
||||
assert!(queue.is_empty());
|
||||
}
|
||||
|
||||
// --- Endpoint tests ---
|
||||
|
||||
#[test]
|
||||
fn new_endpoint_has_no_waiters() {
|
||||
let ep = test_endpoint();
|
||||
assert!(!ep.has_waiting_sender());
|
||||
assert!(!ep.has_waiting_receiver());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sender_waits_then_dequeues() {
|
||||
let mut ep = test_endpoint();
|
||||
ep.enqueue_sender(thread(1)).unwrap();
|
||||
|
||||
assert!(ep.has_waiting_sender());
|
||||
assert_eq!(ep.dequeue_sender(), Some(thread(1)));
|
||||
assert!(!ep.has_waiting_sender());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receiver_waits_then_dequeues() {
|
||||
let mut ep = test_endpoint();
|
||||
ep.enqueue_receiver(thread(1)).unwrap();
|
||||
|
||||
assert!(ep.has_waiting_receiver());
|
||||
assert_eq!(ep.dequeue_receiver(), Some(thread(1)));
|
||||
assert!(!ep.has_waiting_receiver());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn senders_and_receivers_are_independent() {
|
||||
let mut ep = test_endpoint();
|
||||
ep.enqueue_sender(thread(1)).unwrap();
|
||||
ep.enqueue_receiver(thread(2)).unwrap();
|
||||
|
||||
assert_eq!(ep.dequeue_sender(), Some(thread(1)));
|
||||
assert_eq!(ep.dequeue_receiver(), Some(thread(2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn revoke_bumps_generation() {
|
||||
let mut ep = test_endpoint();
|
||||
let gen_before = *ep.generation();
|
||||
ep.revoke();
|
||||
let gen_after = *ep.generation();
|
||||
|
||||
assert!(!gen_before.is_valid_against(&gen_after));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rendezvous_sender_first() {
|
||||
let mut ep = test_endpoint();
|
||||
|
||||
// sender arrives first, no receiver waiting
|
||||
ep.enqueue_sender(thread(1)).unwrap();
|
||||
assert!(!ep.has_waiting_receiver());
|
||||
|
||||
// receiver arrives, finds a sender
|
||||
assert!(ep.has_waiting_sender());
|
||||
let sender = ep.dequeue_sender().unwrap();
|
||||
assert_eq!(sender, thread(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rendezvous_receiver_first() {
|
||||
let mut ep = test_endpoint();
|
||||
|
||||
// receiver arrives first, no sender waiting
|
||||
ep.enqueue_receiver(thread(1)).unwrap();
|
||||
assert!(!ep.has_waiting_sender());
|
||||
|
||||
// sender arrives, finds a receiver
|
||||
assert!(ep.has_waiting_receiver());
|
||||
let receiver = ep.dequeue_receiver().unwrap();
|
||||
assert_eq!(receiver, thread(1));
|
||||
}
|
||||
35
crates/domain/src/endpoint/wait_queue.rs
Normal file
35
crates/domain/src/endpoint/wait_queue.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use types::id::ThreadId;
|
||||
|
||||
pub const MAX_WAITERS: usize = 16;
|
||||
|
||||
pub struct WaitQueue {
|
||||
entries: [Option<ThreadId>; MAX_WAITERS],
|
||||
count: u8,
|
||||
}
|
||||
|
||||
impl WaitQueue {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
entries: [None; MAX_WAITERS],
|
||||
count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, thread: ThreadId) -> Option<()> {
|
||||
let slot = self.entries.iter_mut().find(|s| s.is_none())?;
|
||||
*slot = Some(thread);
|
||||
self.count += 1;
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) -> Option<ThreadId> {
|
||||
let slot = self.entries.iter_mut().find(|s| s.is_some())?;
|
||||
let thread = slot.take();
|
||||
self.count -= 1;
|
||||
thread
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.count == 0
|
||||
}
|
||||
}
|
||||
11
crates/domain/src/error.rs
Normal file
11
crates/domain/src/error.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use types::{bounded::CapSlot, id::ProcessId, permission::Permission};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum KernelError {
|
||||
InvalidSlot(CapSlot),
|
||||
RevokedCapability(CapSlot),
|
||||
PermissionDenied { slot: CapSlot, needed: Permission },
|
||||
NoFreeSlot,
|
||||
InvalidProcess(ProcessId),
|
||||
WouldBlock,
|
||||
}
|
||||
10
crates/domain/src/fault.rs
Normal file
10
crates/domain/src/fault.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
use types::{address::VirtualAddress, bounded::CapSlot, permission::Permission};
|
||||
|
||||
pub enum Fault {
|
||||
CapabilityViolation { slot: CapSlot, needed: Permission },
|
||||
InvalidCapability { slot: CapSlot },
|
||||
RevokedCapability { slot: CapSlot },
|
||||
InvalidSyscall,
|
||||
MemoryFault { address: VirtualAddress },
|
||||
IllegalInstruction,
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
}
|
||||
#![no_std]
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
||||
pub mod capability;
|
||||
pub mod endpoint;
|
||||
pub mod error;
|
||||
pub mod fault;
|
||||
pub mod message;
|
||||
pub mod process;
|
||||
pub mod resource;
|
||||
pub mod scheduler;
|
||||
pub mod syscall;
|
||||
pub mod thread;
|
||||
|
||||
9
crates/domain/src/message.rs
Normal file
9
crates/domain/src/message.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use types::bounded::CapSlot;
|
||||
|
||||
pub const MAX_MESSAGE_WORDS: usize = 4;
|
||||
pub const MAX_CAP_TRANSFERS: usize = 4;
|
||||
|
||||
pub struct Message {
|
||||
words: [usize; MAX_MESSAGE_WORDS],
|
||||
caps: [Option<CapSlot>; MAX_CAP_TRANSFERS],
|
||||
}
|
||||
15
crates/domain/src/process.rs
Normal file
15
crates/domain/src/process.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use types::{
|
||||
bounded::CapSlot,
|
||||
id::{ProcessId, ThreadId},
|
||||
};
|
||||
|
||||
use crate::capability::CapabilityTable;
|
||||
|
||||
pub const MAX_THREADS_PER_PROCESS: usize = 4;
|
||||
|
||||
pub struct Process {
|
||||
id: ProcessId,
|
||||
capabilities: CapabilityTable,
|
||||
threads: [Option<ThreadId>; MAX_THREADS_PER_PROCESS],
|
||||
fault_handler: Option<CapSlot>,
|
||||
}
|
||||
22
crates/domain/src/resource.rs
Normal file
22
crates/domain/src/resource.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use types::{
|
||||
address::VirtualAddress,
|
||||
bounded::PageCount,
|
||||
generation::Generation,
|
||||
id::{EndpointId, IrqId, ProcessId, RegionId},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Resource {
|
||||
Memory(RegionId),
|
||||
Endpoint(EndpointId),
|
||||
Process(ProcessId),
|
||||
Interrupt(IrqId),
|
||||
ProcessCreate,
|
||||
}
|
||||
|
||||
pub struct MemoryRegion {
|
||||
id: RegionId,
|
||||
base: VirtualAddress,
|
||||
size: PageCount,
|
||||
generation: Generation,
|
||||
}
|
||||
24
crates/domain/src/scheduler/mod.rs
Normal file
24
crates/domain/src/scheduler/mod.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use types::{
|
||||
bounded::Priority,
|
||||
id::{CoreId, ThreadId},
|
||||
};
|
||||
|
||||
pub mod per_core;
|
||||
pub mod priority_queue;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests.rs"]
|
||||
mod tests;
|
||||
|
||||
pub enum SchedulerDecision {
|
||||
Continue,
|
||||
Preempt(ThreadId),
|
||||
}
|
||||
|
||||
pub trait Scheduler {
|
||||
fn add(&mut self, thread: ThreadId, priority: Priority, core: CoreId);
|
||||
fn remove(&mut self, thread: ThreadId);
|
||||
fn next(&mut self, core: CoreId) -> Option<ThreadId>;
|
||||
fn tick(&mut self, core: CoreId, current: ThreadId) -> SchedulerDecision;
|
||||
fn balance(&mut self);
|
||||
}
|
||||
52
crates/domain/src/scheduler/per_core.rs
Normal file
52
crates/domain/src/scheduler/per_core.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use super::priority_queue::PriorityQueue;
|
||||
use types::bounded::Priority;
|
||||
use types::id::{CoreId, ThreadId};
|
||||
|
||||
pub struct PerCoreScheduler {
|
||||
core: CoreId,
|
||||
queue: PriorityQueue,
|
||||
current: Option<ThreadId>,
|
||||
}
|
||||
|
||||
impl PerCoreScheduler {
|
||||
pub fn new(core: CoreId) -> Self {
|
||||
Self {
|
||||
core,
|
||||
queue: PriorityQueue::new(),
|
||||
current: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn core(&self) -> CoreId {
|
||||
self.core
|
||||
}
|
||||
|
||||
pub fn current(&self) -> Option<ThreadId> {
|
||||
self.current
|
||||
}
|
||||
|
||||
pub fn add(&mut self, thread: ThreadId, priority: Priority) {
|
||||
self.queue.add(thread, priority);
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, thread: ThreadId, priority: Priority) {
|
||||
self.queue.remove(thread, priority);
|
||||
}
|
||||
|
||||
pub fn pick_next(&mut self) -> Option<ThreadId> {
|
||||
let (thread, _priority) = self.queue.next()?;
|
||||
self.current = Some(thread);
|
||||
Some(thread)
|
||||
}
|
||||
|
||||
pub fn clear_current(&mut self) {
|
||||
self.current = None;
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
// for load balancing — count of ready threads
|
||||
// could track this incrementally instead of computing
|
||||
// but for now, bitmap popcount works
|
||||
self.queue.bitmap().count_ones() as usize
|
||||
}
|
||||
}
|
||||
108
crates/domain/src/scheduler/priority_queue.rs
Normal file
108
crates/domain/src/scheduler/priority_queue.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use types::bounded::Priority;
|
||||
use types::id::ThreadId;
|
||||
|
||||
const NUM_PRIORITIES: usize = 32;
|
||||
const MAX_THREADS_PER_PRIORITY: usize = 16;
|
||||
|
||||
struct RunList {
|
||||
threads: [Option<ThreadId>; MAX_THREADS_PER_PRIORITY],
|
||||
head: usize,
|
||||
tail: usize,
|
||||
count: u8,
|
||||
}
|
||||
|
||||
impl RunList {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
threads: [None; MAX_THREADS_PER_PRIORITY],
|
||||
head: 0,
|
||||
tail: 0,
|
||||
count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, thread: ThreadId) -> bool {
|
||||
match self.threads.iter_mut().find(|s| s.is_none()) {
|
||||
Some(slot) => {
|
||||
*slot = Some(thread);
|
||||
self.count += 1;
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn pop(&mut self) -> Option<ThreadId> {
|
||||
if self.count == 0 {
|
||||
return None;
|
||||
}
|
||||
let slot = self.threads.iter_mut().find(|s| s.is_some())?;
|
||||
let thread = slot.take();
|
||||
self.count -= 1;
|
||||
thread
|
||||
}
|
||||
|
||||
fn remove(&mut self, thread: ThreadId) -> bool {
|
||||
for slot in &mut self.threads {
|
||||
if *slot == Some(thread) {
|
||||
*slot = None;
|
||||
self.count -= 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.count == 0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PriorityQueue {
|
||||
queues: [RunList; NUM_PRIORITIES],
|
||||
bitmap: u32,
|
||||
}
|
||||
|
||||
impl PriorityQueue {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
queues: core::array::from_fn(|_| RunList::new()),
|
||||
bitmap: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bitmap(&self) -> u32 {
|
||||
self.bitmap
|
||||
}
|
||||
|
||||
pub fn add(&mut self, thread: ThreadId, priority: Priority) {
|
||||
let idx = priority.value() as usize;
|
||||
self.queues[idx].push(thread);
|
||||
self.bitmap |= 1 << idx;
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, thread: ThreadId, priority: Priority) {
|
||||
let idx = priority.value() as usize;
|
||||
self.queues[idx].remove(thread);
|
||||
if self.queues[idx].is_empty() {
|
||||
self.bitmap &= !(1 << idx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> Option<(ThreadId, Priority)> {
|
||||
if self.bitmap == 0 {
|
||||
return None;
|
||||
}
|
||||
// highest set bit = highest priority
|
||||
let idx = 31 - self.bitmap.leading_zeros() as usize;
|
||||
let thread = self.queues[idx].pop()?;
|
||||
if self.queues[idx].is_empty() {
|
||||
self.bitmap &= !(1 << idx);
|
||||
}
|
||||
Some((thread, Priority::new(idx as u8).unwrap()))
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.bitmap == 0
|
||||
}
|
||||
}
|
||||
124
crates/domain/src/scheduler/tests.rs
Normal file
124
crates/domain/src/scheduler/tests.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
use super::per_core::PerCoreScheduler;
|
||||
use super::priority_queue::PriorityQueue;
|
||||
use types::bounded::Priority;
|
||||
use types::id::{CoreId, ThreadId};
|
||||
|
||||
fn thread(id: u16) -> ThreadId {
|
||||
ThreadId::from_raw(id)
|
||||
}
|
||||
|
||||
fn prio(value: u8) -> Priority {
|
||||
Priority::new(value).unwrap()
|
||||
}
|
||||
|
||||
// --- PriorityQueue tests ---
|
||||
|
||||
#[test]
|
||||
fn empty_queue_returns_none() {
|
||||
let mut q = PriorityQueue::new();
|
||||
assert!(q.next().is_none());
|
||||
assert!(q.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_thread() {
|
||||
let mut q = PriorityQueue::new();
|
||||
q.add(thread(1), prio(10));
|
||||
|
||||
let (t, p) = q.next().unwrap();
|
||||
assert_eq!(t, thread(1));
|
||||
assert_eq!(p, prio(10));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn highest_priority_runs_first() {
|
||||
let mut q = PriorityQueue::new();
|
||||
q.add(thread(1), prio(5));
|
||||
q.add(thread(2), prio(20));
|
||||
q.add(thread(3), prio(10));
|
||||
|
||||
let (t, _) = q.next().unwrap();
|
||||
assert_eq!(t, thread(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_priority_is_fifo() {
|
||||
let mut q = PriorityQueue::new();
|
||||
q.add(thread(1), prio(10));
|
||||
q.add(thread(2), prio(10));
|
||||
q.add(thread(3), prio(10));
|
||||
|
||||
assert_eq!(q.next().unwrap().0, thread(1));
|
||||
assert_eq!(q.next().unwrap().0, thread(2));
|
||||
assert_eq!(q.next().unwrap().0, thread(3));
|
||||
assert!(q.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_thread() {
|
||||
let mut q = PriorityQueue::new();
|
||||
q.add(thread(1), prio(10));
|
||||
q.add(thread(2), prio(10));
|
||||
|
||||
q.remove(thread(1), prio(10));
|
||||
|
||||
let (t, _) = q.next().unwrap();
|
||||
assert_eq!(t, thread(2));
|
||||
assert!(q.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_clears_bitmap_when_empty() {
|
||||
let mut q = PriorityQueue::new();
|
||||
q.add(thread(1), prio(10));
|
||||
q.remove(thread(1), prio(10));
|
||||
|
||||
assert!(q.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drain_all_priorities() {
|
||||
let mut q = PriorityQueue::new();
|
||||
q.add(thread(1), prio(0));
|
||||
q.add(thread(2), prio(15));
|
||||
q.add(thread(3), prio(31));
|
||||
|
||||
assert_eq!(q.next().unwrap().0, thread(3)); // highest first
|
||||
assert_eq!(q.next().unwrap().0, thread(2));
|
||||
assert_eq!(q.next().unwrap().0, thread(1)); // lowest last
|
||||
assert!(q.is_empty());
|
||||
}
|
||||
|
||||
// --- PerCoreScheduler tests ---
|
||||
|
||||
#[test]
|
||||
fn new_core_has_no_current() {
|
||||
let core = PerCoreScheduler::new(CoreId::from_raw(0));
|
||||
assert!(core.current().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pick_next_sets_current() {
|
||||
let mut core = PerCoreScheduler::new(CoreId::from_raw(0));
|
||||
core.add(thread(1), prio(10));
|
||||
|
||||
let picked = core.pick_next().unwrap();
|
||||
assert_eq!(picked, thread(1));
|
||||
assert_eq!(core.current(), Some(thread(1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pick_next_on_empty_returns_none() {
|
||||
let mut core = PerCoreScheduler::new(CoreId::from_raw(0));
|
||||
assert!(core.pick_next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_current() {
|
||||
let mut core = PerCoreScheduler::new(CoreId::from_raw(0));
|
||||
core.add(thread(1), prio(10));
|
||||
core.pick_next();
|
||||
|
||||
core.clear_current();
|
||||
assert!(core.current().is_none());
|
||||
}
|
||||
64
crates/domain/src/syscall.rs
Normal file
64
crates/domain/src/syscall.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use types::{
|
||||
address::{DataRef, EntryPoint, StackPointer, VirtualAddress},
|
||||
bounded::{CapSlot, PageCount},
|
||||
permission::PermissionSet,
|
||||
};
|
||||
|
||||
use crate::message::Message;
|
||||
|
||||
pub struct WaitSet {
|
||||
endpoints: [Option<CapSlot>; 4],
|
||||
interrupts: [Option<CapSlot>; 4],
|
||||
}
|
||||
|
||||
pub enum WakeReason {
|
||||
MessageReady(CapSlot),
|
||||
InterruptFired(CapSlot),
|
||||
}
|
||||
|
||||
pub enum Syscall {
|
||||
Send {
|
||||
endpoint: CapSlot,
|
||||
message: Message,
|
||||
},
|
||||
Receive {
|
||||
endpoint: CapSlot,
|
||||
},
|
||||
Delegate {
|
||||
slot: CapSlot,
|
||||
to_endpoint: CapSlot,
|
||||
permissions: PermissionSet,
|
||||
},
|
||||
WaitForInterrupt {
|
||||
irq: CapSlot,
|
||||
},
|
||||
AcknowledgeInterrupt {
|
||||
irq: CapSlot,
|
||||
},
|
||||
WaitAny {
|
||||
sources: WaitSet,
|
||||
},
|
||||
CreateProcess {
|
||||
create_cap: CapSlot,
|
||||
image: DataRef,
|
||||
},
|
||||
Kill {
|
||||
process: CapSlot,
|
||||
},
|
||||
SpawnThread {
|
||||
entry: EntryPoint,
|
||||
stack: StackPointer,
|
||||
},
|
||||
ExitThread,
|
||||
RequestPages {
|
||||
count: PageCount,
|
||||
},
|
||||
ReleasePages {
|
||||
addr: VirtualAddress,
|
||||
count: PageCount,
|
||||
},
|
||||
GetTime,
|
||||
GetProcessId,
|
||||
Yield,
|
||||
Exit,
|
||||
}
|
||||
33
crates/domain/src/thread.rs
Normal file
33
crates/domain/src/thread.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use types::{
|
||||
bounded::{CapSlot, Priority},
|
||||
id::{CoreId, ProcessId, ThreadId},
|
||||
time::TimeSlice,
|
||||
};
|
||||
|
||||
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],
|
||||
}
|
||||
|
||||
pub enum BlockReason {
|
||||
Sending(CapSlot),
|
||||
Receiving(CapSlot),
|
||||
WaitingForInterrupt(CapSlot),
|
||||
WaitingAny,
|
||||
}
|
||||
Reference in New Issue
Block a user