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:
2026-10-26 19:54:23 +01:00
parent 8d2c40cb1e
commit d43421e33e
32 changed files with 790 additions and 207 deletions

View File

@@ -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) {

View File

@@ -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);
}