refactor(kernel): split ipc/memory/lib/thread by concern; trim comments
This commit is contained in:
@@ -2,10 +2,7 @@
|
||||
//! capability is a node with at most one parent. Kept free of kernel
|
||||
//! types so the revocation rule can be checked on its own.
|
||||
|
||||
/// `result[i]` is true iff `root` is `i` or an ancestor of `i`.
|
||||
///
|
||||
/// `parents` must be acyclic, which holds because a capability's parent
|
||||
/// always exists before it does.
|
||||
/// `result[i]` is true iff `root` is `i` or an ancestor of `i`. `parents` must be acyclic.
|
||||
pub fn descendants<const N: usize>(parents: &[Option<u16>; N], root: usize) -> [bool; N] {
|
||||
let mut marked = [false; N];
|
||||
let Some(r) = marked.get_mut(root) else {
|
||||
@@ -57,9 +54,7 @@ mod proofs {
|
||||
false
|
||||
}
|
||||
|
||||
/// On every acyclic forest of `N` capabilities, `descendants` marks
|
||||
/// exactly the nodes `root` is an ancestor of: revocation removes the
|
||||
/// whole subtree and nothing else.
|
||||
/// On every acyclic forest, `descendants` marks exactly the subtree under `root`.
|
||||
#[kani::proof]
|
||||
#[kani::unwind(6)]
|
||||
fn marks_exactly_the_subtree() {
|
||||
|
||||
@@ -24,9 +24,7 @@ mod tests;
|
||||
// Sized so that every representable `CapSlot` indexes a real slot.
|
||||
pub const MAX_CAPS_PER_PROCESS: usize = CapSlot::MAX as usize + 1;
|
||||
|
||||
/// Names one capability slot at one point in time. Both generations must
|
||||
/// still match for the reference to resolve, so a reused pid or slot is
|
||||
/// never mistaken for the capability that used to live there.
|
||||
/// One capability slot at one point in time; both generations must match to resolve.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct CapRef {
|
||||
pub process: ProcessId,
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
//! The capability derivation tree with explicit links, so deleting a
|
||||
//! capability costs its number of children and revoking costs the size of
|
||||
//! the subtree, instead of a scan of every capability in the system.
|
||||
//!
|
||||
//! Nodes are numbered `0..N` (the kernel flattens process and slot). Each
|
||||
//! live node knows its parent, its first child and its siblings. The
|
||||
//! fixpoint in `derivation::descendants` stays as the reference this tree
|
||||
//! is checked against.
|
||||
//! Derivation tree with explicit links: delete costs its children, revoke its subtree.
|
||||
//! Nodes are `0..N`; `derivation::descendants` is the reference model it is checked against.
|
||||
|
||||
/// Longest walk any operation may take: every node visited once, plus the
|
||||
/// climb back up. Corrupted links can therefore never hang the kernel.
|
||||
@@ -59,9 +53,7 @@ impl<const N: usize> CapTree<N> {
|
||||
self.node(i)?.parent.map(usize::from)
|
||||
}
|
||||
|
||||
/// Adds `i` as a child of `parent` (or as a root). Fails, changing
|
||||
/// nothing, if `i` is already live or out of range, or `parent` is not
|
||||
/// live.
|
||||
/// Adds `i` under `parent` (or as a root); fails without change if `i` is live or out of range, or `parent` is not live.
|
||||
pub fn attach(&mut self, i: usize, parent: Option<usize>) -> bool {
|
||||
let (Some(me), Some(node)) = (id(i), self.nodes.get(i)) else {
|
||||
return false;
|
||||
|
||||
@@ -85,9 +85,7 @@ impl<const N: usize> Default for WaitQueue<N> {
|
||||
mod proofs {
|
||||
use super::*;
|
||||
|
||||
/// Against a simple reference model (a plain array kept in arrival
|
||||
/// order), any sequence of pushes, pops and removals behaves the same:
|
||||
/// FIFO order, no duplicates, nothing lost.
|
||||
/// Matches a plain arrival-ordered array model: FIFO, no duplicates, nothing lost.
|
||||
#[kani::proof]
|
||||
#[kani::unwind(7)]
|
||||
fn behaves_like_a_fifo_without_duplicates() {
|
||||
|
||||
@@ -6,9 +6,7 @@ use types::generation::Generation;
|
||||
#[path = "generations_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
/// A slot's generation changes every time the object in it is destroyed
|
||||
/// or revoked, so capabilities minted earlier stop matching. A slot whose
|
||||
/// counter runs out is retired for good rather than reused.
|
||||
/// Bumped on every destroy or revoke so older capabilities stop matching; a slot whose counter runs out is retired.
|
||||
pub struct SlotGenerations<const N: usize> {
|
||||
gens: [Option<Generation>; N],
|
||||
}
|
||||
|
||||
@@ -104,9 +104,7 @@ impl<const N: usize> FrameTable<N> {
|
||||
self.frames.get(self.index_of(pa)?)?.as_ref()
|
||||
}
|
||||
|
||||
/// Next-fit: searches from just past the previous allocation and wraps
|
||||
/// once, so a run of allocations costs O(1) each instead of rescanning
|
||||
/// the allocated prefix every time.
|
||||
/// Next-fit: searches from just past the previous allocation, wrapping once.
|
||||
pub fn alloc(&mut self, usage: FrameUse) -> Option<PhysicalAddress> {
|
||||
if self.free == 0 {
|
||||
return None;
|
||||
@@ -177,10 +175,7 @@ impl<const N: usize> FrameTable<N> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The frame mapped at `va` in `owner`, if `owner` holds it as a user page.
|
||||
/// The perms of the frame at `pa` if it is `owner`'s user page at
|
||||
/// `va`; `None` for any other frame. O(1): the caller supplies the
|
||||
/// frame, this only confirms it.
|
||||
/// The perms of the frame at `pa` if it is `owner`'s user page at `va`; O(1), the caller supplies the frame.
|
||||
pub fn user_at(
|
||||
&self,
|
||||
pa: PhysicalAddress,
|
||||
|
||||
@@ -17,9 +17,7 @@ struct ListEnds {
|
||||
tail: Option<ThreadId>,
|
||||
}
|
||||
|
||||
/// Ready queue: one FIFO per priority level, threaded through an
|
||||
/// intrusive doubly linked list indexed by thread id. A thread is in at
|
||||
/// most one list, so capacity never runs out and every op is O(1).
|
||||
/// Ready queue: one FIFO per priority in an intrusive list indexed by thread id; O(1) ops.
|
||||
pub struct PriorityQueue {
|
||||
lists: [ListEnds; NUM_PRIORITIES],
|
||||
links: [Links; MAX_THREADS],
|
||||
|
||||
51
crates/domain/src/thread/context.rs
Normal file
51
crates/domain/src/thread/context.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
//! Saved register state of a thread.
|
||||
|
||||
pub struct SavedContext {
|
||||
registers: [usize; 32],
|
||||
pc: usize,
|
||||
status: usize,
|
||||
}
|
||||
|
||||
impl SavedContext {
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
registers: [0; 32],
|
||||
pc: 0,
|
||||
status: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_entry_and_stack(
|
||||
entry: types::address::EntryPoint,
|
||||
stack: types::address::StackPointer,
|
||||
) -> Self {
|
||||
let mut ctx = Self::empty();
|
||||
ctx.pc = entry.raw();
|
||||
ctx.registers[2] = stack.raw();
|
||||
ctx
|
||||
}
|
||||
|
||||
pub fn program_counter(&self) -> usize {
|
||||
self.pc
|
||||
}
|
||||
|
||||
pub fn set_pc(&mut self, pc: usize) {
|
||||
self.pc = pc;
|
||||
}
|
||||
|
||||
pub fn status(&self) -> usize {
|
||||
self.status
|
||||
}
|
||||
|
||||
pub fn set_status(&mut self, status: usize) {
|
||||
self.status = status;
|
||||
}
|
||||
|
||||
pub fn registers(&self) -> &[usize; 32] {
|
||||
&self.registers
|
||||
}
|
||||
|
||||
pub fn registers_mut(&mut self) -> &mut [usize; 32] {
|
||||
&mut self.registers
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,16 @@
|
||||
use types::{
|
||||
bounded::Priority,
|
||||
generation::Generation,
|
||||
id::{CoreId, EndpointId, NotificationId, ProcessId, ThreadId},
|
||||
time::TimeSlice,
|
||||
};
|
||||
|
||||
use crate::{error::KernelError, generations::SlotGenerations};
|
||||
use crate::error::KernelError;
|
||||
|
||||
mod context;
|
||||
mod table;
|
||||
|
||||
pub use context::SavedContext;
|
||||
pub use table::{MAX_THREADS, ThreadTable};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests.rs"]
|
||||
@@ -29,56 +34,6 @@ pub enum ThreadState {
|
||||
Dead,
|
||||
}
|
||||
|
||||
pub struct SavedContext {
|
||||
registers: [usize; 32],
|
||||
pc: usize,
|
||||
status: usize,
|
||||
}
|
||||
|
||||
impl SavedContext {
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
registers: [0; 32],
|
||||
pc: 0,
|
||||
status: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_entry_and_stack(
|
||||
entry: types::address::EntryPoint,
|
||||
stack: types::address::StackPointer,
|
||||
) -> Self {
|
||||
let mut ctx = Self::empty();
|
||||
ctx.pc = entry.raw();
|
||||
ctx.registers[2] = stack.raw();
|
||||
ctx
|
||||
}
|
||||
|
||||
pub fn program_counter(&self) -> usize {
|
||||
self.pc
|
||||
}
|
||||
|
||||
pub fn set_pc(&mut self, pc: usize) {
|
||||
self.pc = pc;
|
||||
}
|
||||
|
||||
pub fn status(&self) -> usize {
|
||||
self.status
|
||||
}
|
||||
|
||||
pub fn set_status(&mut self, status: usize) {
|
||||
self.status = status;
|
||||
}
|
||||
|
||||
pub fn registers(&self) -> &[usize; 32] {
|
||||
&self.registers
|
||||
}
|
||||
|
||||
pub fn registers_mut(&mut self) -> &mut [usize; 32] {
|
||||
&mut self.registers
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BlockReason {
|
||||
/// Queued as a sender on the endpoint.
|
||||
@@ -228,129 +183,3 @@ impl Thread {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const MAX_THREADS: usize = if cfg!(kani) { 3 } else { 256 };
|
||||
|
||||
pub struct ThreadTable {
|
||||
threads: [Option<Thread>; MAX_THREADS],
|
||||
generations: SlotGenerations<MAX_THREADS>,
|
||||
}
|
||||
|
||||
impl ThreadTable {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
threads: [const { None }; MAX_THREADS],
|
||||
generations: SlotGenerations::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Current generation of a live thread, so references to it (reply
|
||||
/// slots) can detect that the id was reused.
|
||||
pub fn generation(&self, id: ThreadId) -> Option<Generation> {
|
||||
self.get(id).ok()?;
|
||||
self.generations.current(id.raw() as usize)
|
||||
}
|
||||
|
||||
fn free_slot(&self) -> Option<usize> {
|
||||
self.threads
|
||||
.iter()
|
||||
.enumerate()
|
||||
.position(|(i, t)| t.is_none() && self.generations.is_usable(i))
|
||||
}
|
||||
|
||||
pub fn spawn(
|
||||
&mut self,
|
||||
process: ProcessId,
|
||||
priority: Priority,
|
||||
time_slice: TimeSlice,
|
||||
core_affinity: Option<CoreId>,
|
||||
) -> Result<ThreadId, KernelError> {
|
||||
let idx = self.free_slot().ok_or(KernelError::TooManyThreads)?;
|
||||
|
||||
let id = ThreadId::from_raw(idx as u16);
|
||||
*self
|
||||
.threads
|
||||
.get_mut(idx)
|
||||
.ok_or(KernelError::TooManyThreads)? = Some(Thread::new(
|
||||
id,
|
||||
process,
|
||||
priority,
|
||||
time_slice,
|
||||
core_affinity,
|
||||
));
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub fn spawn_with_context(
|
||||
&mut self,
|
||||
process: ProcessId,
|
||||
priority: Priority,
|
||||
time_slice: TimeSlice,
|
||||
core_affinity: Option<CoreId>,
|
||||
context: SavedContext,
|
||||
) -> Result<ThreadId, KernelError> {
|
||||
let idx = self.free_slot().ok_or(KernelError::TooManyThreads)?;
|
||||
|
||||
let id = ThreadId::from_raw(idx as u16);
|
||||
*self
|
||||
.threads
|
||||
.get_mut(idx)
|
||||
.ok_or(KernelError::TooManyThreads)? = Some(Thread::with_context(
|
||||
id,
|
||||
process,
|
||||
priority,
|
||||
time_slice,
|
||||
core_affinity,
|
||||
context,
|
||||
));
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
/// Every live thread with its id.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (ThreadId, &Thread)> {
|
||||
self.threads
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, t)| Some((ThreadId::from_raw(i as u16), t.as_ref()?)))
|
||||
}
|
||||
|
||||
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<Thread, KernelError> {
|
||||
let idx = id.raw() as usize;
|
||||
let thread = self
|
||||
.threads
|
||||
.get_mut(idx)
|
||||
.and_then(|s| s.take())
|
||||
.ok_or(KernelError::InvalidThread(id))?;
|
||||
self.generations.advance(idx);
|
||||
Ok(thread)
|
||||
}
|
||||
|
||||
pub fn threads_for_process(&self, process: ProcessId) -> impl Iterator<Item = &Thread> {
|
||||
self.threads
|
||||
.iter()
|
||||
.filter_map(|s| s.as_ref())
|
||||
.filter(move |t| t.process() == process)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ThreadTable {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
137
crates/domain/src/thread/table.rs
Normal file
137
crates/domain/src/thread/table.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
//! Fixed-capacity table of threads.
|
||||
|
||||
use types::{
|
||||
bounded::Priority,
|
||||
generation::Generation,
|
||||
id::{CoreId, ProcessId, ThreadId},
|
||||
time::TimeSlice,
|
||||
};
|
||||
|
||||
use super::{SavedContext, Thread};
|
||||
use crate::{error::KernelError, generations::SlotGenerations};
|
||||
|
||||
pub const MAX_THREADS: usize = if cfg!(kani) { 3 } else { 256 };
|
||||
|
||||
pub struct ThreadTable {
|
||||
threads: [Option<Thread>; MAX_THREADS],
|
||||
generations: SlotGenerations<MAX_THREADS>,
|
||||
}
|
||||
|
||||
impl ThreadTable {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
threads: [const { None }; MAX_THREADS],
|
||||
generations: SlotGenerations::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Current generation of a live thread, so references to it (reply
|
||||
/// slots) can detect that the id was reused.
|
||||
pub fn generation(&self, id: ThreadId) -> Option<Generation> {
|
||||
self.get(id).ok()?;
|
||||
self.generations.current(id.raw() as usize)
|
||||
}
|
||||
|
||||
fn free_slot(&self) -> Option<usize> {
|
||||
self.threads
|
||||
.iter()
|
||||
.enumerate()
|
||||
.position(|(i, t)| t.is_none() && self.generations.is_usable(i))
|
||||
}
|
||||
|
||||
pub fn spawn(
|
||||
&mut self,
|
||||
process: ProcessId,
|
||||
priority: Priority,
|
||||
time_slice: TimeSlice,
|
||||
core_affinity: Option<CoreId>,
|
||||
) -> Result<ThreadId, KernelError> {
|
||||
let idx = self.free_slot().ok_or(KernelError::TooManyThreads)?;
|
||||
|
||||
let id = ThreadId::from_raw(idx as u16);
|
||||
*self
|
||||
.threads
|
||||
.get_mut(idx)
|
||||
.ok_or(KernelError::TooManyThreads)? = Some(Thread::new(
|
||||
id,
|
||||
process,
|
||||
priority,
|
||||
time_slice,
|
||||
core_affinity,
|
||||
));
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub fn spawn_with_context(
|
||||
&mut self,
|
||||
process: ProcessId,
|
||||
priority: Priority,
|
||||
time_slice: TimeSlice,
|
||||
core_affinity: Option<CoreId>,
|
||||
context: SavedContext,
|
||||
) -> Result<ThreadId, KernelError> {
|
||||
let idx = self.free_slot().ok_or(KernelError::TooManyThreads)?;
|
||||
|
||||
let id = ThreadId::from_raw(idx as u16);
|
||||
*self
|
||||
.threads
|
||||
.get_mut(idx)
|
||||
.ok_or(KernelError::TooManyThreads)? = Some(Thread::with_context(
|
||||
id,
|
||||
process,
|
||||
priority,
|
||||
time_slice,
|
||||
core_affinity,
|
||||
context,
|
||||
));
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
/// Every live thread with its id.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (ThreadId, &Thread)> {
|
||||
self.threads
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, t)| Some((ThreadId::from_raw(i as u16), t.as_ref()?)))
|
||||
}
|
||||
|
||||
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<Thread, KernelError> {
|
||||
let idx = id.raw() as usize;
|
||||
let thread = self
|
||||
.threads
|
||||
.get_mut(idx)
|
||||
.and_then(|s| s.take())
|
||||
.ok_or(KernelError::InvalidThread(id))?;
|
||||
self.generations.advance(idx);
|
||||
Ok(thread)
|
||||
}
|
||||
|
||||
pub fn threads_for_process(&self, process: ProcessId) -> impl Iterator<Item = &Thread> {
|
||||
self.threads
|
||||
.iter()
|
||||
.filter_map(|s| s.as_ref())
|
||||
.filter(move |t| t.process() == process)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ThreadTable {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user