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:
@@ -1,10 +1,11 @@
|
||||
use types::{
|
||||
bounded::{CapSlot, Priority},
|
||||
id::{CoreId, ProcessId, ThreadId},
|
||||
bounded::Priority,
|
||||
generation::Generation,
|
||||
id::{CoreId, EndpointId, NotificationId, ProcessId, ThreadId},
|
||||
time::TimeSlice,
|
||||
};
|
||||
|
||||
use crate::error::KernelError;
|
||||
use crate::{error::KernelError, generations::SlotGenerations};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests.rs"]
|
||||
@@ -80,10 +81,16 @@ impl SavedContext {
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BlockReason {
|
||||
Sending(CapSlot),
|
||||
Receiving(CapSlot),
|
||||
WaitingForInterrupt(CapSlot),
|
||||
WaitingAny,
|
||||
/// Queued as a sender on the endpoint.
|
||||
Sending(EndpointId),
|
||||
/// Queued as a `Call`er on the endpoint; will then await the reply.
|
||||
Calling(EndpointId),
|
||||
/// Queued as a receiver on the endpoint.
|
||||
Receiving(EndpointId),
|
||||
/// Delivered a `Call`; waiting for the server's reply.
|
||||
AwaitingReply,
|
||||
/// Blocked in `Wait` on the notification.
|
||||
WaitingNotification(NotificationId),
|
||||
}
|
||||
|
||||
impl Thread {
|
||||
@@ -180,6 +187,17 @@ impl Thread {
|
||||
}
|
||||
}
|
||||
|
||||
/// Keeps a blocked thread blocked, waiting for something else.
|
||||
pub fn rewait(&mut self, reason: BlockReason) -> Result<(), KernelError> {
|
||||
match self.state {
|
||||
ThreadState::Blocked(_) => {
|
||||
self.state = ThreadState::Blocked(reason);
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(KernelError::InvalidStateTransition(self.id)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unblock(&mut self) -> Result<(), KernelError> {
|
||||
match self.state {
|
||||
ThreadState::Blocked(_) => {
|
||||
@@ -215,15 +233,28 @@ pub const MAX_THREADS: usize = 256;
|
||||
|
||||
pub struct ThreadTable {
|
||||
threads: [Option<Thread>; MAX_THREADS],
|
||||
generations: SlotGenerations<MAX_THREADS>,
|
||||
}
|
||||
|
||||
impl ThreadTable {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
threads: [const { None }; MAX_THREADS],
|
||||
generations: SlotGenerations::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Current generation of a live thread, so references to it (reply
|
||||
/// slots) can detect that the id was reused.
|
||||
pub fn generation(&self, id: ThreadId) -> Option<Generation> {
|
||||
self.get(id).ok()?;
|
||||
self.generations.current(id.raw() as usize)
|
||||
}
|
||||
|
||||
fn free_slot(&self) -> Option<usize> {
|
||||
(0..MAX_THREADS).find(|&i| self.threads[i].is_none() && self.generations.is_usable(i))
|
||||
}
|
||||
|
||||
pub fn spawn(
|
||||
&mut self,
|
||||
process: ProcessId,
|
||||
@@ -231,11 +262,7 @@ impl ThreadTable {
|
||||
time_slice: TimeSlice,
|
||||
core_affinity: Option<CoreId>,
|
||||
) -> Result<ThreadId, KernelError> {
|
||||
let idx = self
|
||||
.threads
|
||||
.iter()
|
||||
.position(|s| s.is_none())
|
||||
.ok_or(KernelError::TooManyThreads)?;
|
||||
let idx = self.free_slot().ok_or(KernelError::TooManyThreads)?;
|
||||
|
||||
let id = ThreadId::from_raw(idx as u16);
|
||||
self.threads[idx] = Some(Thread::new(
|
||||
@@ -256,11 +283,7 @@ impl ThreadTable {
|
||||
core_affinity: Option<CoreId>,
|
||||
context: SavedContext,
|
||||
) -> Result<ThreadId, KernelError> {
|
||||
let idx = self
|
||||
.threads
|
||||
.iter()
|
||||
.position(|s| s.is_none())
|
||||
.ok_or(KernelError::TooManyThreads)?;
|
||||
let idx = self.free_slot().ok_or(KernelError::TooManyThreads)?;
|
||||
|
||||
let id = ThreadId::from_raw(idx as u16);
|
||||
self.threads[idx] = Some(Thread::with_context(
|
||||
@@ -292,10 +315,13 @@ impl ThreadTable {
|
||||
|
||||
pub fn destroy(&mut self, id: ThreadId) -> Result<Thread, KernelError> {
|
||||
let idx = id.raw() as usize;
|
||||
self.threads
|
||||
let thread = self
|
||||
.threads
|
||||
.get_mut(idx)
|
||||
.and_then(|s| s.take())
|
||||
.ok_or(KernelError::InvalidThread(id))
|
||||
.ok_or(KernelError::InvalidThread(id))?;
|
||||
self.generations.advance(idx);
|
||||
Ok(thread)
|
||||
}
|
||||
|
||||
pub fn threads_for_process(&self, process: ProcessId) -> impl Iterator<Item = &Thread> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::*;
|
||||
use types::bounded::Priority;
|
||||
use types::id::{CoreId, ProcessId, ThreadId};
|
||||
use types::id::{CoreId, EndpointId, ProcessId, ThreadId};
|
||||
use types::time::TimeSlice;
|
||||
|
||||
fn thread(id: u16) -> ThreadId {
|
||||
@@ -65,10 +65,10 @@ fn ready_to_running() {
|
||||
fn running_to_blocked() {
|
||||
let mut t = test_thread(0, 0);
|
||||
t.set_running(core(0)).unwrap();
|
||||
t.block(BlockReason::WaitingAny).unwrap();
|
||||
t.block(BlockReason::AwaitingReply).unwrap();
|
||||
assert!(matches!(
|
||||
t.state(),
|
||||
ThreadState::Blocked(BlockReason::WaitingAny)
|
||||
ThreadState::Blocked(BlockReason::AwaitingReply)
|
||||
));
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ fn running_to_blocked() {
|
||||
fn blocked_to_ready() {
|
||||
let mut t = test_thread(0, 0);
|
||||
t.set_running(core(0)).unwrap();
|
||||
t.block(BlockReason::WaitingAny).unwrap();
|
||||
t.block(BlockReason::AwaitingReply).unwrap();
|
||||
t.unblock().unwrap();
|
||||
assert!(matches!(t.state(), ThreadState::Ready));
|
||||
}
|
||||
@@ -105,7 +105,7 @@ fn dead_cannot_transition() {
|
||||
t.set_running(core(0)).unwrap();
|
||||
t.exit().unwrap();
|
||||
assert!(t.set_running(core(0)).is_err());
|
||||
assert!(t.block(BlockReason::WaitingAny).is_err());
|
||||
assert!(t.block(BlockReason::AwaitingReply).is_err());
|
||||
assert!(t.unblock().is_err());
|
||||
assert!(t.preempt().is_err());
|
||||
assert!(t.exit().is_err());
|
||||
@@ -114,14 +114,14 @@ fn dead_cannot_transition() {
|
||||
#[test]
|
||||
fn ready_cannot_block() {
|
||||
let mut t = test_thread(0, 0);
|
||||
assert!(t.block(BlockReason::WaitingAny).is_err());
|
||||
assert!(t.block(BlockReason::AwaitingReply).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocked_cannot_run_directly() {
|
||||
let mut t = test_thread(0, 0);
|
||||
t.set_running(core(0)).unwrap();
|
||||
t.block(BlockReason::WaitingAny).unwrap();
|
||||
t.block(BlockReason::AwaitingReply).unwrap();
|
||||
assert!(t.set_running(core(0)).is_err());
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ fn ready_cannot_exit() {
|
||||
fn blocked_cannot_preempt() {
|
||||
let mut t = test_thread(0, 0);
|
||||
t.set_running(core(0)).unwrap();
|
||||
t.block(BlockReason::WaitingAny).unwrap();
|
||||
t.block(BlockReason::AwaitingReply).unwrap();
|
||||
assert!(t.preempt().is_err());
|
||||
}
|
||||
|
||||
@@ -237,3 +237,32 @@ fn table_destroy_reuses_slot() {
|
||||
.unwrap();
|
||||
assert_eq!(id, new_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_blocked_thread_can_change_what_it_waits_for() {
|
||||
let mut t = Thread::new(
|
||||
ThreadId::from_raw(1),
|
||||
ProcessId::from_raw(0),
|
||||
Priority::DEFAULT,
|
||||
TimeSlice::DEFAULT,
|
||||
None,
|
||||
);
|
||||
t.set_running(CoreId::from_raw(0)).unwrap();
|
||||
t.block(BlockReason::Calling(EndpointId::from_raw(0)))
|
||||
.unwrap();
|
||||
t.rewait(BlockReason::AwaitingReply).unwrap();
|
||||
assert_eq!(*t.state(), ThreadState::Blocked(BlockReason::AwaitingReply));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_blocked_threads_can_rewait() {
|
||||
let mut t = Thread::new(
|
||||
ThreadId::from_raw(1),
|
||||
ProcessId::from_raw(0),
|
||||
Priority::DEFAULT,
|
||||
TimeSlice::DEFAULT,
|
||||
None,
|
||||
);
|
||||
assert!(t.rewait(BlockReason::AwaitingReply).is_err());
|
||||
assert_eq!(*t.state(), ThreadState::Ready);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user