feat(kernel): object lifetimes via slot generations
- SlotGenerations per table, never wraps, retires exhausted slots - caps to dead/reused process, endpoint, region rejected - endpoints owned, die w/ owner, waiters woken w/ error - DropCap syscall + libkos wrapper - irq table bounds-checked, routes dropped w/ thread - self-kill terminates caller, exit_thread full cleanup - sched run_now: switch to named thread, not queue head - lifetime tests, fuzz kills/recreates, kani gen proofs
This commit is contained in:
@@ -83,7 +83,7 @@ fn full_table_returns_no_free_slot() {
|
||||
#[test]
|
||||
fn generation_mismatch_detected() {
|
||||
let gen_v1 = Generation::initial();
|
||||
let gen_v2 = gen_v1.next();
|
||||
let gen_v2 = gen_v1.next().unwrap();
|
||||
|
||||
assert!(gen_v1.is_valid_against(&gen_v1));
|
||||
assert!(!gen_v1.is_valid_against(&gen_v2));
|
||||
|
||||
@@ -4,7 +4,10 @@ use types::{
|
||||
id::{EndpointId, ProcessId, ThreadId},
|
||||
};
|
||||
|
||||
use crate::{endpoint::wait_queue::WaitQueue, error::KernelError, message::Message};
|
||||
use crate::{
|
||||
endpoint::wait_queue::WaitQueue, error::KernelError, generations::SlotGenerations,
|
||||
message::Message,
|
||||
};
|
||||
|
||||
pub mod wait_queue;
|
||||
|
||||
@@ -14,18 +17,19 @@ mod tests;
|
||||
|
||||
pub struct Endpoint {
|
||||
id: EndpointId,
|
||||
/// The process that created it; the endpoint dies with its owner.
|
||||
owner: ProcessId,
|
||||
waiting_senders: WaitQueue,
|
||||
waiting_receivers: WaitQueue,
|
||||
generation: Generation,
|
||||
}
|
||||
|
||||
impl Endpoint {
|
||||
pub fn new(id: EndpointId) -> Self {
|
||||
pub fn new(id: EndpointId, owner: ProcessId) -> Self {
|
||||
Self {
|
||||
id,
|
||||
owner,
|
||||
waiting_senders: WaitQueue::new(),
|
||||
waiting_receivers: WaitQueue::new(),
|
||||
generation: Generation::initial(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +37,8 @@ impl Endpoint {
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub fn generation(&self) -> &Generation {
|
||||
&self.generation
|
||||
pub fn owner(&self) -> ProcessId {
|
||||
self.owner
|
||||
}
|
||||
|
||||
pub fn enqueue_sender(&mut self, thread: ThreadId) -> Option<()> {
|
||||
@@ -68,32 +72,52 @@ impl Endpoint {
|
||||
pub fn remove_receiver(&mut self, thread: ThreadId) -> bool {
|
||||
self.waiting_receivers.remove_thread(thread)
|
||||
}
|
||||
|
||||
pub fn revoke(&mut self) {
|
||||
self.generation = self.generation.next();
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_ENDPOINTS: usize = 256;
|
||||
pub const MAX_ENDPOINTS: usize = 256;
|
||||
|
||||
pub struct EndpointTable {
|
||||
endpoints: [Option<Endpoint>; MAX_ENDPOINTS],
|
||||
generations: SlotGenerations<MAX_ENDPOINTS>,
|
||||
}
|
||||
|
||||
impl EndpointTable {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
endpoints: [const { None }; MAX_ENDPOINTS],
|
||||
generations: SlotGenerations::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(&mut self) -> Option<EndpointId> {
|
||||
let idx = self.endpoints.iter().position(|s| s.is_none())?;
|
||||
pub fn create(&mut self, owner: ProcessId) -> Option<EndpointId> {
|
||||
let idx = (0..MAX_ENDPOINTS)
|
||||
.find(|&i| self.endpoints[i].is_none() && self.generations.is_usable(i))?;
|
||||
let id = EndpointId::from_raw(idx as u16);
|
||||
self.endpoints[idx] = Some(Endpoint::new(id));
|
||||
self.endpoints[idx] = Some(Endpoint::new(id, owner));
|
||||
Some(id)
|
||||
}
|
||||
|
||||
/// Generation capabilities to a live endpoint must carry.
|
||||
pub fn generation(&self, id: EndpointId) -> Option<Generation> {
|
||||
self.get(id)?;
|
||||
self.generations.current(id.raw() as usize)
|
||||
}
|
||||
|
||||
/// Invalidates every existing capability to `id`; the endpoint lives on.
|
||||
pub fn revoke(&mut self, id: EndpointId) {
|
||||
if self.get(id).is_some() {
|
||||
self.generations.advance(id.raw() as usize);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ids_owned_by(&self, owner: ProcessId) -> impl Iterator<Item = EndpointId> + '_ {
|
||||
self.endpoints
|
||||
.iter()
|
||||
.flatten()
|
||||
.filter(move |ep| ep.owner == owner)
|
||||
.map(|ep| ep.id)
|
||||
}
|
||||
|
||||
pub fn get(&self, id: EndpointId) -> Option<&Endpoint> {
|
||||
self.endpoints
|
||||
.get(id.raw() as usize)
|
||||
@@ -106,10 +130,12 @@ impl EndpointTable {
|
||||
.and_then(|s| s.as_mut())
|
||||
}
|
||||
|
||||
/// Removes the endpoint; capabilities to it become invalid even if the
|
||||
/// slot is later reused.
|
||||
pub fn destroy(&mut self, id: EndpointId) -> Option<Endpoint> {
|
||||
self.endpoints
|
||||
.get_mut(id.raw() as usize)
|
||||
.and_then(|s| s.take())
|
||||
let ep = self.endpoints.get_mut(id.raw() as usize)?.take()?;
|
||||
self.generations.advance(id.raw() as usize);
|
||||
Some(ep)
|
||||
}
|
||||
|
||||
pub fn remove_thread_from_all(&mut self, thread: ThreadId) {
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
use crate::endpoint::wait_queue::MAX_WAITERS;
|
||||
|
||||
use super::*;
|
||||
use types::id::{EndpointId, ThreadId};
|
||||
use types::id::{EndpointId, ProcessId, ThreadId};
|
||||
|
||||
fn thread(id: u16) -> ThreadId {
|
||||
ThreadId::from_raw(id)
|
||||
}
|
||||
|
||||
fn owner() -> ProcessId {
|
||||
ProcessId::from_raw(0)
|
||||
}
|
||||
|
||||
fn test_endpoint() -> Endpoint {
|
||||
Endpoint::new(EndpointId::from_raw(1))
|
||||
Endpoint::new(EndpointId::from_raw(1), owner())
|
||||
}
|
||||
|
||||
// --- WaitQueue tests ---
|
||||
@@ -99,13 +103,46 @@ fn senders_and_receivers_are_independent() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn revoke_bumps_generation() {
|
||||
let mut ep = test_endpoint();
|
||||
let gen_before = *ep.generation();
|
||||
ep.revoke();
|
||||
let gen_after = *ep.generation();
|
||||
fn revoke_bumps_generation_but_keeps_the_endpoint() {
|
||||
let mut table = EndpointTable::new();
|
||||
let id = table.create(owner()).unwrap();
|
||||
let gen_before = table.generation(id).unwrap();
|
||||
table.revoke(id);
|
||||
let gen_after = table.generation(id).unwrap();
|
||||
|
||||
assert!(!gen_before.is_valid_against(&gen_after));
|
||||
assert!(table.get(id).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reused_slot_gets_a_new_generation() {
|
||||
let mut table = EndpointTable::new();
|
||||
let id = table.create(owner()).unwrap();
|
||||
let old = table.generation(id).unwrap();
|
||||
table.destroy(id);
|
||||
assert_eq!(
|
||||
table.generation(id),
|
||||
None,
|
||||
"dead endpoints have no generation"
|
||||
);
|
||||
let again = table.create(owner()).unwrap();
|
||||
assert_eq!(again, id);
|
||||
assert!(!old.is_valid_against(&table.generation(again).unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ids_owned_by_lists_only_that_owners_endpoints() {
|
||||
let mut table = EndpointTable::new();
|
||||
let a = table.create(owner()).unwrap();
|
||||
table.create(ProcessId::from_raw(1)).unwrap();
|
||||
let c = table.create(owner()).unwrap();
|
||||
let mut ids = [EndpointId::from_raw(u16::MAX); 4];
|
||||
let mut n = 0;
|
||||
for id in table.ids_owned_by(owner()) {
|
||||
ids[n] = id;
|
||||
n += 1;
|
||||
}
|
||||
assert_eq!(&ids[..n], &[a, c]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -141,22 +178,22 @@ fn rendezvous_receiver_first() {
|
||||
#[test]
|
||||
fn table_create_and_lookup() {
|
||||
let mut table = EndpointTable::new();
|
||||
let id = table.create().unwrap();
|
||||
let id = table.create(owner()).unwrap();
|
||||
assert!(table.get(id).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_create_returns_unique_ids() {
|
||||
let mut table = EndpointTable::new();
|
||||
let a = table.create().unwrap();
|
||||
let b = table.create().unwrap();
|
||||
let a = table.create(owner()).unwrap();
|
||||
let b = table.create(owner()).unwrap();
|
||||
assert_ne!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_destroy_then_get_fails() {
|
||||
let mut table = EndpointTable::new();
|
||||
let id = table.create().unwrap();
|
||||
let id = table.create(owner()).unwrap();
|
||||
let removed = table.destroy(id);
|
||||
assert!(removed.is_some());
|
||||
assert!(table.get(id).is_none());
|
||||
@@ -165,7 +202,7 @@ fn table_destroy_then_get_fails() {
|
||||
#[test]
|
||||
fn table_get_mut_allows_enqueue() {
|
||||
let mut table = EndpointTable::new();
|
||||
let id = table.create().unwrap();
|
||||
let id = table.create(owner()).unwrap();
|
||||
table
|
||||
.get_mut(id)
|
||||
.unwrap()
|
||||
@@ -177,8 +214,8 @@ fn table_get_mut_allows_enqueue() {
|
||||
#[test]
|
||||
fn table_destroy_reuses_slot() {
|
||||
let mut table = EndpointTable::new();
|
||||
let id = table.create().unwrap();
|
||||
let id = table.create(owner()).unwrap();
|
||||
table.destroy(id);
|
||||
let new_id = table.create().unwrap();
|
||||
let new_id = table.create(owner()).unwrap();
|
||||
assert_eq!(id, new_id);
|
||||
}
|
||||
|
||||
@@ -31,4 +31,6 @@ pub enum KernelError {
|
||||
/// The target virtual range already holds a mapping.
|
||||
AddressInUse,
|
||||
InvalidRegion,
|
||||
/// The object a capability would refer to no longer exists.
|
||||
NoSuchObject,
|
||||
}
|
||||
|
||||
86
crates/domain/src/generations.rs
Normal file
86
crates/domain/src/generations.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
//! Per-slot generation counters shared by the kernel's object tables.
|
||||
|
||||
use types::generation::Generation;
|
||||
|
||||
#[cfg(test)]
|
||||
#[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.
|
||||
pub struct SlotGenerations<const N: usize> {
|
||||
gens: [Option<Generation>; N],
|
||||
}
|
||||
|
||||
impl<const N: usize> SlotGenerations<N> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
gens: [Some(Generation::initial()); N],
|
||||
}
|
||||
}
|
||||
|
||||
/// Current generation of `idx`, `None` if retired or out of range.
|
||||
pub fn current(&self, idx: usize) -> Option<Generation> {
|
||||
*self.gens.get(idx)?
|
||||
}
|
||||
|
||||
pub fn is_usable(&self, idx: usize) -> bool {
|
||||
self.current(idx).is_some()
|
||||
}
|
||||
|
||||
/// Invalidates every capability minted for the current generation.
|
||||
pub fn advance(&mut self, idx: usize) {
|
||||
if let Some(g) = self.gens.get_mut(idx) {
|
||||
*g = g.and_then(|g| g.next());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Default for SlotGenerations<N> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(kani)]
|
||||
mod proofs {
|
||||
use super::SlotGenerations;
|
||||
use types::generation::Generation;
|
||||
|
||||
/// However many times a slot is advanced, a generation observed earlier
|
||||
/// never comes back, so a capability minted for it stays dead.
|
||||
#[kani::proof]
|
||||
#[kani::unwind(5)]
|
||||
fn an_observed_generation_never_returns() {
|
||||
let mut g: SlotGenerations<1> = SlotGenerations::new();
|
||||
g.gens[0] = if kani::any() {
|
||||
Some(Generation::from_raw(kani::any()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let observed = g.current(0);
|
||||
let steps: u8 = kani::any();
|
||||
kani::assume(steps >= 1 && steps <= 4);
|
||||
for _ in 0..steps {
|
||||
g.advance(0);
|
||||
if let (Some(old), Some(now)) = (observed, g.current(0)) {
|
||||
assert!(!old.is_valid_against(&now));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A retired slot is never usable again.
|
||||
#[kani::proof]
|
||||
#[kani::unwind(5)]
|
||||
fn retired_slots_stay_retired() {
|
||||
let mut g: SlotGenerations<1> = SlotGenerations::new();
|
||||
g.gens[0] = None;
|
||||
let steps: u8 = kani::any();
|
||||
kani::assume(steps <= 4);
|
||||
for _ in 0..steps {
|
||||
g.advance(0);
|
||||
}
|
||||
assert!(!g.is_usable(0));
|
||||
}
|
||||
}
|
||||
36
crates/domain/src/generations_tests.rs
Normal file
36
crates/domain/src/generations_tests.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use super::SlotGenerations;
|
||||
use types::generation::Generation;
|
||||
|
||||
#[test]
|
||||
fn slots_start_at_the_initial_generation() {
|
||||
let g: SlotGenerations<2> = SlotGenerations::new();
|
||||
assert_eq!(g.current(0), Some(Generation::initial()));
|
||||
assert!(g.is_usable(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn advance_changes_the_generation() {
|
||||
let mut g: SlotGenerations<2> = SlotGenerations::new();
|
||||
let before = g.current(0);
|
||||
g.advance(0);
|
||||
assert_ne!(g.current(0), before);
|
||||
assert_eq!(g.current(1), before, "other slots untouched");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn out_of_range_slots_are_never_usable() {
|
||||
let mut g: SlotGenerations<2> = SlotGenerations::new();
|
||||
assert_eq!(g.current(2), None);
|
||||
g.advance(2); // no panic
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exhausted_counter_retires_the_slot() {
|
||||
let mut g: SlotGenerations<1> = SlotGenerations::new();
|
||||
g.gens[0] = Some(Generation::from_raw(u32::MAX));
|
||||
g.advance(0);
|
||||
assert_eq!(g.current(0), None);
|
||||
assert!(!g.is_usable(0));
|
||||
g.advance(0);
|
||||
assert_eq!(g.current(0), None, "retired slots stay retired");
|
||||
}
|
||||
@@ -20,20 +20,35 @@ impl IrqRoutingTable {
|
||||
}
|
||||
|
||||
pub fn register(&mut self, irq: IrqId, thread: ThreadId) -> Result<(), KernelError> {
|
||||
let idx = irq.raw() as usize;
|
||||
if self.owners[idx].is_some() {
|
||||
let slot = self
|
||||
.owners
|
||||
.get_mut(irq.raw() as usize)
|
||||
.ok_or(KernelError::InvalidIrq(irq))?;
|
||||
if slot.is_some() {
|
||||
return Err(KernelError::IrqAlreadyOwned(irq));
|
||||
}
|
||||
self.owners[idx] = Some(thread);
|
||||
*slot = Some(thread);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unregister(&mut self, irq: IrqId) {
|
||||
self.owners[irq.raw() as usize] = None;
|
||||
if let Some(slot) = self.owners.get_mut(irq.raw() as usize) {
|
||||
*slot = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Drops every route to `thread`, e.g. when it dies.
|
||||
pub fn unregister_thread(&mut self, thread: ThreadId) {
|
||||
for slot in &mut self.owners {
|
||||
if *slot == Some(thread) {
|
||||
*slot = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The thread an interrupt is routed to; `None` for unknown lines.
|
||||
pub fn owner(&self, irq: IrqId) -> Option<ThreadId> {
|
||||
self.owners[irq.raw() as usize]
|
||||
*self.owners.get(irq.raw() as usize)?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ pub mod capability;
|
||||
pub mod endpoint;
|
||||
pub mod error;
|
||||
pub mod fault;
|
||||
pub mod generations;
|
||||
pub mod irq;
|
||||
pub mod memory;
|
||||
pub mod message;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::generations::SlotGenerations;
|
||||
use types::{
|
||||
address::{PhysicalAddress, VirtualAddress},
|
||||
bounded::PageCount,
|
||||
@@ -43,7 +44,6 @@ pub struct Region {
|
||||
backing: RegionBacking,
|
||||
base: PhysicalAddress,
|
||||
pages: PageCount,
|
||||
generation: Generation,
|
||||
mappings: [Option<RegionMapping>; MAX_REGION_MAPPINGS],
|
||||
}
|
||||
|
||||
@@ -59,7 +59,6 @@ impl Region {
|
||||
backing,
|
||||
base,
|
||||
pages,
|
||||
generation: Generation::initial(),
|
||||
mappings: [None; MAX_REGION_MAPPINGS],
|
||||
}
|
||||
}
|
||||
@@ -80,14 +79,6 @@ impl Region {
|
||||
self.pages
|
||||
}
|
||||
|
||||
pub fn generation(&self) -> &Generation {
|
||||
&self.generation
|
||||
}
|
||||
|
||||
pub fn revoke(&mut self) {
|
||||
self.generation = self.generation.next();
|
||||
}
|
||||
|
||||
pub fn mappings(&self) -> impl Iterator<Item = &RegionMapping> {
|
||||
self.mappings.iter().flatten()
|
||||
}
|
||||
@@ -124,23 +115,42 @@ impl Region {
|
||||
|
||||
pub struct RegionTable {
|
||||
regions: [Option<Region>; MAX_REGIONS],
|
||||
generations: SlotGenerations<MAX_REGIONS>,
|
||||
}
|
||||
|
||||
impl RegionTable {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
regions: [const { None }; MAX_REGIONS],
|
||||
generations: SlotGenerations::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generation capabilities to a live region must carry.
|
||||
pub fn generation(&self, id: RegionId) -> Option<Generation> {
|
||||
self.get(id)?;
|
||||
self.generations.current(id.raw() as usize)
|
||||
}
|
||||
|
||||
/// Invalidates every existing capability to `id`; the region lives on.
|
||||
pub fn revoke(&mut self, id: RegionId) {
|
||||
if self.get(id).is_some() {
|
||||
self.generations.advance(id.raw() as usize);
|
||||
}
|
||||
}
|
||||
|
||||
fn free_slot(&self) -> Option<usize> {
|
||||
(0..MAX_REGIONS).find(|&i| self.regions[i].is_none() && self.generations.is_usable(i))
|
||||
}
|
||||
|
||||
/// The id the next `insert` will return.
|
||||
pub fn next_free_id(&self) -> Option<RegionId> {
|
||||
let idx = self.regions.iter().position(|s| s.is_none())?;
|
||||
let idx = self.free_slot()?;
|
||||
Some(RegionId::from_raw(idx as u16))
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, region: Region) -> Option<RegionId> {
|
||||
let idx = self.regions.iter().position(|s| s.is_none())?;
|
||||
let idx = self.free_slot()?;
|
||||
self.regions[idx] = Some(region);
|
||||
Some(RegionId::from_raw(idx as u16))
|
||||
}
|
||||
@@ -153,8 +163,12 @@ impl RegionTable {
|
||||
self.regions.get_mut(id.raw() as usize)?.as_mut()
|
||||
}
|
||||
|
||||
/// Removes the region; capabilities to it become invalid even if the
|
||||
/// slot is later reused.
|
||||
pub fn remove(&mut self, id: RegionId) -> Option<Region> {
|
||||
self.regions.get_mut(id.raw() as usize)?.take()
|
||||
let region = self.regions.get_mut(id.raw() as usize)?.take()?;
|
||||
self.generations.advance(id.raw() as usize);
|
||||
Some(region)
|
||||
}
|
||||
|
||||
pub fn ids_owned_by(&self, owner: ProcessId) -> impl Iterator<Item = RegionId> + '_ {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use types::{
|
||||
bounded::CapSlot,
|
||||
generation::Generation,
|
||||
id::{ProcessId, ThreadId},
|
||||
};
|
||||
|
||||
use crate::{capability::CapabilityTable, error::KernelError};
|
||||
use crate::{capability::CapabilityTable, error::KernelError, generations::SlotGenerations};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests.rs"]
|
||||
@@ -75,24 +76,30 @@ impl Process {
|
||||
|
||||
pub struct ProcessTable {
|
||||
processes: [Option<Process>; MAX_PROCESSES],
|
||||
generations: SlotGenerations<MAX_PROCESSES>,
|
||||
}
|
||||
|
||||
impl ProcessTable {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
processes: [const { None }; MAX_PROCESSES],
|
||||
generations: SlotGenerations::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generation capabilities to a live process must carry.
|
||||
pub fn generation(&self, id: ProcessId) -> Option<Generation> {
|
||||
self.get(id).ok()?;
|
||||
self.generations.current(id.raw() as usize)
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
&mut self,
|
||||
capabilities: CapabilityTable,
|
||||
fault_handler: Option<CapSlot>,
|
||||
) -> Result<ProcessId, KernelError> {
|
||||
let idx = self
|
||||
.processes
|
||||
.iter()
|
||||
.position(|s| s.is_none())
|
||||
let idx = (0..MAX_PROCESSES)
|
||||
.find(|&i| self.processes[i].is_none() && self.generations.is_usable(i))
|
||||
.ok_or(KernelError::TooManyProcesses)?;
|
||||
|
||||
let id = ProcessId::from_raw(idx as u16);
|
||||
@@ -116,12 +123,17 @@ impl ProcessTable {
|
||||
.ok_or(KernelError::InvalidProcess(id))
|
||||
}
|
||||
|
||||
/// Removes the process; capabilities to it become invalid even if the
|
||||
/// pid is later reused.
|
||||
pub fn destroy(&mut self, id: ProcessId) -> Result<Process, KernelError> {
|
||||
let idx = id.raw() as usize;
|
||||
self.processes
|
||||
let process = self
|
||||
.processes
|
||||
.get_mut(idx)
|
||||
.and_then(|s| s.take())
|
||||
.ok_or(KernelError::InvalidProcess(id))
|
||||
.ok_or(KernelError::InvalidProcess(id))?;
|
||||
self.generations.advance(idx);
|
||||
Ok(process)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,14 @@ impl MultiCoreScheduler {
|
||||
self.cores[core.raw() as usize].set_current(thread);
|
||||
}
|
||||
|
||||
/// Runs `thread` next on `core` regardless of its place in the queue.
|
||||
/// Fails if it is not queued there.
|
||||
pub fn run_now(&mut self, core: CoreId, thread: ThreadId) -> bool {
|
||||
self.cores
|
||||
.get_mut(core.raw() as usize)
|
||||
.is_some_and(|c| c.run_now(thread))
|
||||
}
|
||||
|
||||
fn thread_count_on(&self, core: CoreId) -> usize {
|
||||
self.placements
|
||||
.iter()
|
||||
|
||||
@@ -39,6 +39,15 @@ impl PerCoreScheduler {
|
||||
Some(thread)
|
||||
}
|
||||
|
||||
/// Takes `thread` out of the run queue and makes it current.
|
||||
pub fn run_now(&mut self, thread: ThreadId) -> bool {
|
||||
if !self.queue.remove(thread) {
|
||||
return false;
|
||||
}
|
||||
self.current = Some(thread);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn clear_current(&mut self) {
|
||||
self.current = None;
|
||||
}
|
||||
|
||||
@@ -284,3 +284,26 @@ fn removed_thread_is_skipped_and_order_kept() {
|
||||
assert_eq!(q.pop_highest().unwrap().0, thread(3));
|
||||
assert!(q.pop_highest().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_now_picks_the_named_thread_not_the_queue_head() {
|
||||
let mut s = MultiCoreScheduler::new(1);
|
||||
s.add(thread(1), prio(10), core(0));
|
||||
s.add(thread(2), prio(10), core(0));
|
||||
s.add(thread(3), prio(10), core(0));
|
||||
|
||||
assert!(s.run_now(core(0), thread(2)));
|
||||
assert_eq!(s.current(core(0)), Some(thread(2)));
|
||||
// The others keep their order; thread 2 is no longer queued.
|
||||
assert_eq!(s.next(core(0)), Some(thread(1)));
|
||||
assert_eq!(s.next(core(0)), Some(thread(3)));
|
||||
assert_eq!(s.next(core(0)), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_now_rejects_a_thread_that_is_not_queued() {
|
||||
let mut s = MultiCoreScheduler::new(1);
|
||||
s.add(thread(1), prio(10), core(0));
|
||||
assert!(!s.run_now(core(0), thread(9)));
|
||||
assert_eq!(s.current(core(0)), None);
|
||||
}
|
||||
|
||||
@@ -69,6 +69,10 @@ pub enum Syscall {
|
||||
Revoke {
|
||||
slot: CapSlot,
|
||||
},
|
||||
/// Removes a capability from the caller's own table.
|
||||
DropCap {
|
||||
slot: CapSlot,
|
||||
},
|
||||
SpawnThread {
|
||||
entry: EntryPoint,
|
||||
stack: StackPointer,
|
||||
|
||||
@@ -20,6 +20,7 @@ pub struct Thread {
|
||||
core_affinity: Option<CoreId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ThreadState {
|
||||
Ready,
|
||||
Running(CoreId),
|
||||
@@ -77,6 +78,7 @@ impl SavedContext {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BlockReason {
|
||||
Sending(CapSlot),
|
||||
Receiving(CapSlot),
|
||||
|
||||
Reference in New Issue
Block a user