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:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user