feat(kernel): seL4-style IPC, notifications, status ABI

- badges + Mint, Call/Reply/ReplyRecv w/ gen-checked reply slot
- cap transfer (1/msg, copy, needs Delegate, rechecked at delivery)
- notifications: signal/wait/poll/bind, irq -> notification routing
- WaitAny/WaitForInterrupt removed; a0=status, ErrorCode
- libkos: single ecall primitive, compat wrappers + typed new api
- fix: trap return clobbered user t0 on every trap
- fix: wait queues FIFO, delegate keeps badge, fault cap checked
- docs/ipc.md spec, ipc tests, ipc fuzz, kani ntfn + waitqueue
This commit is contained in:
2026-10-26 20:27:38 +01:00
parent d43421e33e
commit 76f10ff1f1
46 changed files with 3339 additions and 1296 deletions

View File

@@ -57,6 +57,14 @@ impl Endpoint {
self.waiting_receivers.pop()
}
pub fn is_sender(&self, thread: ThreadId) -> bool {
self.waiting_senders.contains(thread)
}
pub fn is_receiver(&self, thread: ThreadId) -> bool {
self.waiting_receivers.contains(thread)
}
pub fn has_waiting_sender(&self) -> bool {
!self.waiting_senders.is_empty()
}

View File

@@ -19,21 +19,21 @@ fn test_endpoint() -> Endpoint {
#[test]
fn empty_queue_pop_returns_none() {
let mut queue = WaitQueue::new();
let mut queue: WaitQueue = WaitQueue::new();
assert!(queue.pop().is_none());
assert!(queue.is_empty());
}
#[test]
fn push_then_pop_returns_same_thread() {
let mut queue = WaitQueue::new();
let mut queue: WaitQueue = WaitQueue::new();
queue.push(thread(1)).unwrap();
assert_eq!(queue.pop(), Some(thread(1)));
}
#[test]
fn pop_order_is_fifo() {
let mut queue = WaitQueue::new();
let mut queue: WaitQueue = WaitQueue::new();
queue.push(thread(1)).unwrap();
queue.push(thread(2)).unwrap();
queue.push(thread(3)).unwrap();
@@ -46,7 +46,7 @@ fn pop_order_is_fifo() {
#[test]
fn full_queue_rejects_push() {
let mut queue = WaitQueue::new();
let mut queue: WaitQueue = WaitQueue::new();
for i in 0..MAX_WAITERS {
assert!(queue.push(thread(i as u16)).is_some());
}
@@ -55,7 +55,7 @@ fn full_queue_rejects_push() {
#[test]
fn is_empty_after_drain() {
let mut queue = WaitQueue::new();
let mut queue: WaitQueue = WaitQueue::new();
queue.push(thread(1)).unwrap();
queue.push(thread(2)).unwrap();
queue.pop();
@@ -219,3 +219,40 @@ fn table_destroy_reuses_slot() {
let new_id = table.create(owner()).unwrap();
assert_eq!(id, new_id);
}
#[test]
fn wait_queue_is_fifo_even_after_a_pop_leaves_a_hole() {
let mut q: WaitQueue = WaitQueue::new();
q.push(thread(1)).unwrap();
q.push(thread(2)).unwrap();
assert_eq!(q.pop(), Some(thread(1)));
q.push(thread(3)).unwrap();
assert_eq!(q.pop(), Some(thread(2)), "thread 3 jumped the queue");
assert_eq!(q.pop(), Some(thread(3)));
}
#[test]
fn wait_queue_keeps_order_around_a_removed_thread() {
let mut q: WaitQueue = WaitQueue::new();
for n in 1..=3 {
q.push(thread(n)).unwrap();
}
assert!(q.remove_thread(thread(2)));
q.push(thread(4)).unwrap();
assert_eq!(q.pop(), Some(thread(1)));
assert_eq!(q.pop(), Some(thread(3)));
assert_eq!(q.pop(), Some(thread(4)));
assert_eq!(q.pop(), None);
}
#[test]
fn wait_queue_kani_trace_push_push_remove() {
let mut q: WaitQueue<3> = WaitQueue::new();
q.push(thread(3)).unwrap();
q.push(thread(2)).unwrap();
assert!(q.remove_thread(thread(3)));
assert!(!q.remove_thread(thread(0)));
assert!(!q.remove_thread(thread(3)));
assert_eq!(q.pop(), Some(thread(2)));
assert_eq!(q.pop(), None);
}

View File

@@ -2,51 +2,127 @@ use types::id::ThreadId;
pub const MAX_WAITERS: usize = 16;
pub struct WaitQueue {
entries: [Option<ThreadId>; MAX_WAITERS],
count: u8,
/// Bounded FIFO of blocked threads. Order is preserved across pops and
/// removals, so no waiter can be overtaken.
pub struct WaitQueue<const N: usize = MAX_WAITERS> {
entries: [Option<ThreadId>; N],
len: usize,
}
impl WaitQueue {
impl<const N: usize> WaitQueue<N> {
pub fn new() -> Self {
Self {
entries: [None; MAX_WAITERS],
count: 0,
entries: [None; N],
len: 0,
}
}
pub fn push(&mut self, thread: ThreadId) -> Option<()> {
let slot = self.entries.iter_mut().find(|s| s.is_none())?;
*slot = Some(thread);
self.count += 1;
if self.len == N || self.contains(thread) {
return None;
}
self.entries[self.len] = Some(thread);
self.len += 1;
Some(())
}
pub fn pop(&mut self) -> Option<ThreadId> {
let slot = self.entries.iter_mut().find(|s| s.is_some())?;
let thread = slot.take();
self.count -= 1;
thread
let head = self.entries[0]?;
self.close_gap(0);
Some(head)
}
pub fn remove_thread(&mut self, thread: ThreadId) -> bool {
for slot in &mut self.entries {
if *slot == Some(thread) {
*slot = None;
self.count -= 1;
return true;
match self.entries[..self.len]
.iter()
.position(|e| *e == Some(thread))
{
Some(i) => {
self.close_gap(i);
true
}
None => false,
}
false
}
pub fn contains(&self, thread: ThreadId) -> bool {
self.entries[..self.len].contains(&Some(thread))
}
pub fn is_empty(&self) -> bool {
self.count == 0
self.len == 0
}
fn close_gap(&mut self, i: usize) {
for j in i..self.len - 1 {
self.entries[j] = self.entries[j + 1];
}
self.len -= 1;
self.entries[self.len] = None;
}
}
impl Default for WaitQueue {
impl<const N: usize> Default for WaitQueue<N> {
fn default() -> Self {
Self::new()
}
}
#[cfg(kani)]
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.
#[kani::proof]
#[kani::unwind(7)]
fn behaves_like_a_fifo_without_duplicates() {
const CAP: usize = 3;
let mut q: WaitQueue<CAP> = WaitQueue::new();
let mut model = [0u16; CAP];
let mut len = 0usize;
for _ in 0..5 {
let t: u16 = kani::any::<u16>() % 4;
match kani::any::<u8>() % 3 {
0 => {
let ok = q.push(ThreadId::from_raw(t)).is_some();
let dup = model[..len].contains(&t);
assert!(ok == (!dup && len < CAP));
if ok {
model[len] = t;
len += 1;
}
}
1 => {
let got = q.pop();
if len == 0 {
assert!(got.is_none());
} else {
assert!(got == Some(ThreadId::from_raw(model[0])));
for j in 0..len - 1 {
model[j] = model[j + 1];
}
len -= 1;
}
}
_ => {
let removed = q.remove_thread(ThreadId::from_raw(t));
match model[..len].iter().position(|&m| m == t) {
Some(i) => {
assert!(removed);
for j in i..len - 1 {
model[j] = model[j + 1];
}
len -= 1;
}
None => assert!(!removed),
}
}
}
assert!(q.len == len);
assert!(q.is_empty() == (len == 0));
}
}
}