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

@@ -205,8 +205,13 @@ impl<P: Platform> Kernel<P> {
Some(tid)
}
pub fn create_endpoint(&mut self) -> Result<types::id::EndpointId, KernelError> {
self.endpoints.create().ok_or(KernelError::NoFreeSlot)
/// Creates an endpoint owned by `owner`; it is destroyed with `owner`.
pub fn create_endpoint(
&mut self,
owner: ProcessId,
) -> Result<types::id::EndpointId, KernelError> {
self.processes.get(owner)?;
self.endpoints.create(owner).ok_or(KernelError::NoFreeSlot)
}
pub fn grant_capability(
@@ -237,8 +242,8 @@ impl<P: Platform> Kernel<P> {
if let Ok(t) = self.threads.get(tid)
&& matches!(t.state(), ThreadState::Ready)
&& self.scheduler.run_now(core, tid)
{
self.scheduler.next(core);
let _ = self.threads.get_mut(tid).map(|t| t.set_running(core));
}
}
@@ -274,16 +279,9 @@ impl<P: Platform> Kernel<P> {
};
self.cleanup_threads(&thread_ids);
self.destroy_endpoints_owned_by(pid);
self.teardown_memory(pid);
// Unblock threads in other processes that were blocked waiting
// on endpoints where this process's threads were the other side.
// The cleanup_threads call above already removed this process's
// threads from all endpoint wait queues. Threads blocked waiting
// for this process will eventually time out or be rescheduled
// when the endpoint is revoked.
let _ = self.processes.destroy(pid);
}
@@ -292,6 +290,7 @@ impl<P: Platform> Kernel<P> {
for tid in thread_ids.iter().flatten() {
self.endpoints.remove_thread_from_all(*tid);
self.cleanup_wait_any(*tid);
self.irqs.unregister_thread(*tid);
self.scheduler.remove(*tid);
self.pending_messages[tid.raw() as usize] = None;
self.pending_results[tid.raw() as usize] = None;
@@ -299,6 +298,41 @@ impl<P: Platform> Kernel<P> {
}
}
/// Destroys every endpoint `owner` created, waking anyone blocked on
/// them with an error instead of leaving them blocked forever.
fn destroy_endpoints_owned_by(&mut self, owner: ProcessId) {
let mut owned = [None; domain::endpoint::MAX_ENDPOINTS];
for (slot, id) in owned.iter_mut().zip(self.endpoints.ids_owned_by(owner)) {
*slot = Some(id);
}
for id in owned.into_iter().flatten() {
let Some(mut ep) = self.endpoints.destroy(id) else {
continue;
};
while let Some(tid) = ep.dequeue_sender().or_else(|| ep.dequeue_receiver()) {
self.wake_with_error(tid, KernelError::InvalidEndpoint(id));
}
}
}
/// Makes a blocked thread runnable with `error` as its syscall result.
fn wake_with_error(&mut self, tid: ThreadId, error: KernelError) {
self.cleanup_wait_any(tid);
self.pending_messages[tid.raw() as usize] = None;
let Ok(thread) = self.threads.get_mut(tid) else {
return;
};
if !matches!(thread.state(), ThreadState::Blocked(_)) {
return;
}
let priority = thread.priority();
if thread.unblock().is_ok() {
self.pending_results[tid.raw() as usize] = Some(SyscallResult::Error(error));
let core = self.platform.core_id();
self.scheduler.add(tid, priority, core);
}
}
pub(crate) fn cleanup_wait_any(&mut self, tid: ThreadId) {
if self.wait_sets[tid.raw() as usize].take().is_some() {
self.endpoints.remove_thread_from_all(tid);
@@ -316,41 +350,27 @@ impl<P: Platform> Kernel<P> {
if !cap.permissions().allows(needed) {
return Err(KernelError::PermissionDenied { slot, needed });
}
let current_gen = self.resource_generation(cap.resource());
if !cap.generation().is_valid_against(&current_gen) {
return Err(KernelError::RevokedCapability(slot));
match self.resource_generation(cap.resource()) {
Some(current) if cap.generation().is_valid_against(&current) => Ok(cap),
_ => Err(KernelError::RevokedCapability(slot)),
}
Ok(cap)
}
fn resource_generation(&self, resource: &Resource) -> Generation {
/// The generation a capability to `resource` must carry, or `None` if
/// the object no longer exists.
fn resource_generation(&self, resource: &Resource) -> Option<Generation> {
match resource {
Resource::Endpoint(id) => self
.endpoints
.get(*id)
.map(|ep| *ep.generation())
.unwrap_or_else(Generation::initial),
Resource::Memory(id) => self
.regions
.get(*id)
.map(|r| *r.generation())
.unwrap_or_else(Generation::initial),
_ => Generation::initial(),
Resource::Endpoint(id) => self.endpoints.generation(*id),
Resource::Memory(id) => self.regions.generation(*id),
Resource::Process(pid) => self.processes.generation(*pid),
Resource::Interrupt(_) | Resource::ProcessCreate => Some(Generation::initial()),
}
}
pub fn revoke_resource(&mut self, resource: &Resource) {
match resource {
Resource::Endpoint(id) => {
if let Some(ep) = self.endpoints.get_mut(*id) {
ep.revoke();
}
}
Resource::Memory(id) => {
if let Some(r) = self.regions.get_mut(*id) {
r.revoke();
}
}
Resource::Endpoint(id) => self.endpoints.revoke(*id),
Resource::Memory(id) => self.regions.revoke(*id),
_ => {}
}
}
@@ -361,7 +381,9 @@ impl<P: Platform> Kernel<P> {
resource: Resource,
permissions: PermissionSet,
) -> Result<CapSlot, KernelError> {
let generation = self.resource_generation(&resource);
let generation = self
.resource_generation(&resource)
.ok_or(KernelError::NoSuchObject)?;
let cap = Capability::new(resource, permissions, generation);
self.processes.get_mut(to)?.capabilities_mut().insert(cap)
}