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