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,4 +1,4 @@
|
||||
use types::{bounded::CapSlot, generation::Generation, permission::PermissionSet};
|
||||
use types::{badge::Badge, bounded::CapSlot, generation::Generation, permission::PermissionSet};
|
||||
|
||||
pub mod authority;
|
||||
|
||||
@@ -11,10 +11,12 @@ mod tests;
|
||||
// Sized so that every representable `CapSlot` indexes a real slot.
|
||||
pub const MAX_CAPS_PER_PROCESS: usize = CapSlot::MAX as usize + 1;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Capability {
|
||||
resource: Resource,
|
||||
permissions: PermissionSet,
|
||||
generation: Generation,
|
||||
badge: Badge,
|
||||
}
|
||||
|
||||
impl Capability {
|
||||
@@ -23,9 +25,20 @@ impl Capability {
|
||||
resource,
|
||||
permissions,
|
||||
generation,
|
||||
badge: Badge::NONE,
|
||||
}
|
||||
}
|
||||
|
||||
/// The same capability with `badge` stamped on it.
|
||||
pub fn with_badge(mut self, badge: Badge) -> Self {
|
||||
self.badge = badge;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn badge(&self) -> Badge {
|
||||
self.badge
|
||||
}
|
||||
|
||||
pub fn resource(&self) -> &Resource {
|
||||
&self.resource
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,4 +33,9 @@ pub enum KernelError {
|
||||
InvalidRegion,
|
||||
/// The object a capability would refer to no longer exists.
|
||||
NoSuchObject,
|
||||
/// The capability names the wrong kind of object for this operation.
|
||||
WrongObject,
|
||||
/// `Reply` with an empty reply slot, or a call whose reply was dropped.
|
||||
NoReply,
|
||||
InvalidArgument,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use types::id::{IrqId, ThreadId};
|
||||
|
||||
use crate::error::KernelError;
|
||||
use types::{
|
||||
badge::Badge,
|
||||
generation::Generation,
|
||||
id::{IrqId, NotificationId},
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests.rs"]
|
||||
@@ -8,47 +10,47 @@ mod tests;
|
||||
|
||||
const MAX_IRQS: usize = 256;
|
||||
|
||||
/// Where an interrupt is delivered: a signal on a notification, through a
|
||||
/// capability snapshot (generation and badge) taken when the route was set.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct IrqRoute {
|
||||
pub notification: NotificationId,
|
||||
pub generation: Generation,
|
||||
pub badge: Badge,
|
||||
}
|
||||
|
||||
pub struct IrqRoutingTable {
|
||||
owners: [Option<ThreadId>; MAX_IRQS],
|
||||
routes: [Option<IrqRoute>; MAX_IRQS],
|
||||
}
|
||||
|
||||
impl IrqRoutingTable {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
owners: [None; MAX_IRQS],
|
||||
routes: [None; MAX_IRQS],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(&mut self, irq: IrqId, thread: ThreadId) -> Result<(), KernelError> {
|
||||
let slot = self
|
||||
.owners
|
||||
.get_mut(irq.raw() as usize)
|
||||
.ok_or(KernelError::InvalidIrq(irq))?;
|
||||
if slot.is_some() {
|
||||
return Err(KernelError::IrqAlreadyOwned(irq));
|
||||
/// Routes `irq`, replacing any previous route. Fails for lines beyond
|
||||
/// the table.
|
||||
pub fn set(&mut self, irq: IrqId, route: IrqRoute) -> bool {
|
||||
match self.routes.get_mut(irq.raw() as usize) {
|
||||
Some(slot) => {
|
||||
*slot = Some(route);
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
*slot = Some(thread);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unregister(&mut self, irq: IrqId) {
|
||||
if let Some(slot) = self.owners.get_mut(irq.raw() as usize) {
|
||||
pub fn clear(&mut self, irq: IrqId) {
|
||||
if let Some(slot) = self.routes.get_mut(irq.raw() as usize) {
|
||||
*slot = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Drops every route to `thread`, e.g. when it dies.
|
||||
pub fn unregister_thread(&mut self, thread: ThreadId) {
|
||||
for slot in &mut self.owners {
|
||||
if *slot == Some(thread) {
|
||||
*slot = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The thread an interrupt is routed to; `None` for unknown lines.
|
||||
pub fn owner(&self, irq: IrqId) -> Option<ThreadId> {
|
||||
*self.owners.get(irq.raw() as usize)?
|
||||
/// The route for `irq`; `None` for unknown or unrouted lines.
|
||||
pub fn route(&self, irq: IrqId) -> Option<IrqRoute> {
|
||||
*self.routes.get(irq.raw() as usize)?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,50 +1,43 @@
|
||||
use super::*;
|
||||
use types::id::{IrqId, ThreadId};
|
||||
use types::badge::Badge;
|
||||
use types::generation::Generation;
|
||||
use types::id::{IrqId, NotificationId};
|
||||
|
||||
fn irq(id: u16) -> IrqId {
|
||||
IrqId::from_raw(id)
|
||||
}
|
||||
|
||||
fn thread(id: u16) -> ThreadId {
|
||||
ThreadId::from_raw(id)
|
||||
fn route(n: u16) -> IrqRoute {
|
||||
IrqRoute {
|
||||
notification: NotificationId::from_raw(n),
|
||||
generation: Generation::initial(),
|
||||
badge: Badge::new(1 << n),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_table_has_no_owners() {
|
||||
let table = IrqRoutingTable::new();
|
||||
assert!(table.owner(irq(0)).is_none());
|
||||
assert!(table.owner(irq(10)).is_none());
|
||||
fn new_table_routes_nothing() {
|
||||
let t = IrqRoutingTable::new();
|
||||
assert_eq!(t.route(IrqId::from_raw(10)), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn register_and_lookup() {
|
||||
let mut table = IrqRoutingTable::new();
|
||||
table.register(irq(5), thread(1)).unwrap();
|
||||
assert_eq!(table.owner(irq(5)), Some(thread(1)));
|
||||
fn set_then_route() {
|
||||
let mut t = IrqRoutingTable::new();
|
||||
assert!(t.set(IrqId::from_raw(10), route(3)));
|
||||
assert_eq!(t.route(IrqId::from_raw(10)), Some(route(3)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn register_already_owned_fails() {
|
||||
let mut table = IrqRoutingTable::new();
|
||||
table.register(irq(5), thread(1)).unwrap();
|
||||
assert!(table.register(irq(5), thread(2)).is_err());
|
||||
fn set_replaces_and_clear_removes() {
|
||||
let mut t = IrqRoutingTable::new();
|
||||
t.set(IrqId::from_raw(10), route(3));
|
||||
t.set(IrqId::from_raw(10), route(4));
|
||||
assert_eq!(t.route(IrqId::from_raw(10)), Some(route(4)));
|
||||
t.clear(IrqId::from_raw(10));
|
||||
assert_eq!(t.route(IrqId::from_raw(10)), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unregister_then_reregister() {
|
||||
let mut table = IrqRoutingTable::new();
|
||||
table.register(irq(5), thread(1)).unwrap();
|
||||
table.unregister(irq(5));
|
||||
assert!(table.owner(irq(5)).is_none());
|
||||
table.register(irq(5), thread(2)).unwrap();
|
||||
assert_eq!(table.owner(irq(5)), Some(thread(2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn different_irqs_independent() {
|
||||
let mut table = IrqRoutingTable::new();
|
||||
table.register(irq(0), thread(10)).unwrap();
|
||||
table.register(irq(1), thread(20)).unwrap();
|
||||
assert_eq!(table.owner(irq(0)), Some(thread(10)));
|
||||
assert_eq!(table.owner(irq(1)), Some(thread(20)));
|
||||
fn lines_beyond_the_table_are_rejected_without_panicking() {
|
||||
let mut t = IrqRoutingTable::new();
|
||||
assert!(!t.set(IrqId::from_raw(1000), route(1)));
|
||||
assert_eq!(t.route(IrqId::from_raw(1000)), None);
|
||||
t.clear(IrqId::from_raw(1000));
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ pub mod generations;
|
||||
pub mod irq;
|
||||
pub mod memory;
|
||||
pub mod message;
|
||||
pub mod notification;
|
||||
pub mod process;
|
||||
pub mod resource;
|
||||
pub mod scheduler;
|
||||
|
||||
@@ -5,26 +5,26 @@ use types::bounded::CapSlot;
|
||||
mod tests;
|
||||
|
||||
pub const MAX_MESSAGE_WORDS: usize = 4;
|
||||
pub const MAX_CAP_TRANSFERS: usize = 4;
|
||||
|
||||
/// Four words and at most one capability (named by the sender's slot).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Message {
|
||||
words: [usize; MAX_MESSAGE_WORDS],
|
||||
caps: [Option<CapSlot>; MAX_CAP_TRANSFERS],
|
||||
cap: Option<CapSlot>,
|
||||
}
|
||||
|
||||
impl Message {
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
words: [0; MAX_MESSAGE_WORDS],
|
||||
caps: [None; MAX_CAP_TRANSFERS],
|
||||
}
|
||||
Self::from_words([0; MAX_MESSAGE_WORDS])
|
||||
}
|
||||
|
||||
pub fn from_words(words: [usize; MAX_MESSAGE_WORDS]) -> Self {
|
||||
Self {
|
||||
words,
|
||||
caps: [None; MAX_CAP_TRANSFERS],
|
||||
}
|
||||
Self { words, cap: None }
|
||||
}
|
||||
|
||||
pub fn with_cap(mut self, slot: CapSlot) -> Self {
|
||||
self.cap = Some(slot);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn words(&self) -> &[usize; MAX_MESSAGE_WORDS] {
|
||||
@@ -35,42 +35,7 @@ impl Message {
|
||||
self.words[index]
|
||||
}
|
||||
|
||||
pub fn caps(&self) -> &[Option<CapSlot>; MAX_CAP_TRANSFERS] {
|
||||
&self.caps
|
||||
}
|
||||
|
||||
pub fn cap(&self, index: usize) -> Option<CapSlot> {
|
||||
self.caps[index]
|
||||
}
|
||||
|
||||
pub fn builder() -> MessageBuilder {
|
||||
MessageBuilder {
|
||||
message: Message::empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MessageBuilder {
|
||||
message: Message,
|
||||
}
|
||||
|
||||
impl MessageBuilder {
|
||||
pub fn word(mut self, index: usize, value: usize) -> Self {
|
||||
self.message.words[index] = value;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn words(mut self, words: [usize; MAX_MESSAGE_WORDS]) -> Self {
|
||||
self.message.words = words;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn cap(mut self, index: usize, slot: CapSlot) -> Self {
|
||||
self.message.caps[index] = Some(slot);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Message {
|
||||
self.message
|
||||
pub fn cap(&self) -> Option<CapSlot> {
|
||||
self.cap
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,78 +1,23 @@
|
||||
use super::*;
|
||||
use types::bounded::CapSlot;
|
||||
|
||||
fn slot(n: u8) -> CapSlot {
|
||||
CapSlot::new(n).unwrap()
|
||||
}
|
||||
|
||||
// --- Construction ---
|
||||
|
||||
#[test]
|
||||
fn empty_message_has_zeroed_words_and_no_caps() {
|
||||
fn empty_message_has_zeroed_words_and_no_cap() {
|
||||
let msg = Message::empty();
|
||||
assert_eq!(msg.words(), &[0; MAX_MESSAGE_WORDS]);
|
||||
for i in 0..MAX_CAP_TRANSFERS {
|
||||
assert!(msg.cap(i).is_none());
|
||||
}
|
||||
assert!(msg.cap().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_words_sets_words_and_no_caps() {
|
||||
fn from_words_keeps_every_word() {
|
||||
let msg = Message::from_words([10, 20, 30, 40]);
|
||||
assert_eq!(msg.word(0), 10);
|
||||
assert_eq!(msg.word(1), 20);
|
||||
assert_eq!(msg.word(2), 30);
|
||||
assert_eq!(msg.word(3), 40);
|
||||
assert!(msg.cap(0).is_none());
|
||||
}
|
||||
|
||||
// --- Accessors ---
|
||||
|
||||
#[test]
|
||||
fn words_returns_full_array() {
|
||||
let msg = Message::from_words([1, 2, 3, 4]);
|
||||
assert_eq!(msg.words(), &[1, 2, 3, 4]);
|
||||
assert_eq!(msg.words(), &[10, 20, 30, 40]);
|
||||
assert!(msg.cap().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caps_returns_full_array() {
|
||||
let msg = Message::empty();
|
||||
assert_eq!(msg.caps(), &[None; MAX_CAP_TRANSFERS]);
|
||||
}
|
||||
|
||||
// --- Builder ---
|
||||
|
||||
#[test]
|
||||
fn builder_sets_individual_words() {
|
||||
let msg = Message::builder().word(0, 42).word(2, 99).build();
|
||||
assert_eq!(msg.word(0), 42);
|
||||
assert_eq!(msg.word(1), 0);
|
||||
assert_eq!(msg.word(2), 99);
|
||||
assert_eq!(msg.word(3), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builder_sets_caps() {
|
||||
let msg = Message::builder().cap(0, slot(5)).cap(1, slot(10)).build();
|
||||
assert_eq!(msg.cap(0), Some(slot(5)));
|
||||
assert_eq!(msg.cap(1), Some(slot(10)));
|
||||
assert!(msg.cap(2).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builder_sets_words_and_caps_together() {
|
||||
let msg = Message::builder()
|
||||
.word(0, 100)
|
||||
.word(1, 200)
|
||||
.cap(0, slot(3))
|
||||
.build();
|
||||
assert_eq!(msg.word(0), 100);
|
||||
assert_eq!(msg.word(1), 200);
|
||||
assert_eq!(msg.cap(0), Some(slot(3)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builder_with_all_words() {
|
||||
let msg = Message::builder().words([11, 22, 33, 44]).build();
|
||||
assert_eq!(msg.words(), &[11, 22, 33, 44]);
|
||||
fn with_cap_attaches_one_capability() {
|
||||
let msg = Message::from_words([1, 2, 3, 4]).with_cap(CapSlot::new(5).unwrap());
|
||||
assert_eq!(msg.cap(), CapSlot::new(5));
|
||||
assert_eq!(msg.word(3), 4);
|
||||
}
|
||||
|
||||
186
crates/domain/src/notification/mod.rs
Normal file
186
crates/domain/src/notification/mod.rs
Normal file
@@ -0,0 +1,186 @@
|
||||
//! Asynchronous signals: a word of bits, a FIFO of waiters and at most one
|
||||
//! bound thread. See docs/ipc.md.
|
||||
|
||||
use types::{
|
||||
badge::Badge,
|
||||
generation::Generation,
|
||||
id::{NotificationId, ProcessId, ThreadId},
|
||||
};
|
||||
|
||||
use crate::{endpoint::wait_queue::WaitQueue, generations::SlotGenerations};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests.rs"]
|
||||
mod tests;
|
||||
|
||||
#[cfg(kani)]
|
||||
#[path = "proofs.rs"]
|
||||
mod proofs;
|
||||
|
||||
pub const MAX_NOTIFICATIONS: usize = 128;
|
||||
|
||||
/// What the kernel must do after a signal.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SignalEffect {
|
||||
/// Wake this blocked `Wait`er with these bits.
|
||||
WakeWaiter(ThreadId, usize),
|
||||
/// Wake the bound thread out of its `Receive` with these bits.
|
||||
WakeBound(ThreadId, usize),
|
||||
/// Nobody to wake; the bits were stored.
|
||||
Stored,
|
||||
}
|
||||
|
||||
pub struct Notification {
|
||||
id: NotificationId,
|
||||
owner: ProcessId,
|
||||
bits: usize,
|
||||
waiters: WaitQueue,
|
||||
bound: Option<ThreadId>,
|
||||
}
|
||||
|
||||
impl Notification {
|
||||
pub fn new(id: NotificationId, owner: ProcessId) -> Self {
|
||||
Self {
|
||||
id,
|
||||
owner,
|
||||
bits: 0,
|
||||
waiters: WaitQueue::new(),
|
||||
bound: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> NotificationId {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn owner(&self) -> ProcessId {
|
||||
self.owner
|
||||
}
|
||||
|
||||
pub fn bound(&self) -> Option<ThreadId> {
|
||||
self.bound
|
||||
}
|
||||
|
||||
/// Delivers a signal. `bound_is_receiving` says whether the bound
|
||||
/// thread is currently blocked in `Receive` and can be woken.
|
||||
pub fn signal(&mut self, badge: Badge, bound_is_receiving: bool) -> SignalEffect {
|
||||
let bits = if badge.is_none() { 1 } else { badge.raw() };
|
||||
if let Some(waiter) = self.waiters.pop() {
|
||||
return SignalEffect::WakeWaiter(waiter, bits);
|
||||
}
|
||||
if let (Some(bound), true) = (self.bound, bound_is_receiving) {
|
||||
return SignalEffect::WakeBound(bound, bits);
|
||||
}
|
||||
self.bits |= bits;
|
||||
SignalEffect::Stored
|
||||
}
|
||||
|
||||
/// Returns and clears the pending bits.
|
||||
pub fn take_bits(&mut self) -> usize {
|
||||
core::mem::take(&mut self.bits)
|
||||
}
|
||||
|
||||
pub fn has_bits(&self) -> bool {
|
||||
self.bits != 0
|
||||
}
|
||||
|
||||
pub fn is_waiter(&self, thread: ThreadId) -> bool {
|
||||
self.waiters.contains(thread)
|
||||
}
|
||||
|
||||
pub fn add_waiter(&mut self, thread: ThreadId) -> bool {
|
||||
self.waiters.push(thread).is_some()
|
||||
}
|
||||
|
||||
pub fn bind(&mut self, thread: ThreadId) -> bool {
|
||||
if self.bound.is_some() {
|
||||
return false;
|
||||
}
|
||||
self.bound = Some(thread);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn unbind(&mut self, thread: ThreadId) {
|
||||
if self.bound == Some(thread) {
|
||||
self.bound = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Forgets `thread` as waiter and as bound thread.
|
||||
pub fn remove_thread(&mut self, thread: ThreadId) {
|
||||
self.waiters.remove_thread(thread);
|
||||
self.unbind(thread);
|
||||
}
|
||||
|
||||
/// Drains every waiter, e.g. when the notification is destroyed.
|
||||
pub fn pop_waiter(&mut self) -> Option<ThreadId> {
|
||||
self.waiters.pop()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NotificationTable {
|
||||
slots: [Option<Notification>; MAX_NOTIFICATIONS],
|
||||
generations: SlotGenerations<MAX_NOTIFICATIONS>,
|
||||
}
|
||||
|
||||
impl NotificationTable {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
slots: [const { None }; MAX_NOTIFICATIONS],
|
||||
generations: SlotGenerations::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(&mut self, owner: ProcessId) -> Option<NotificationId> {
|
||||
let idx = (0..MAX_NOTIFICATIONS)
|
||||
.find(|&i| self.slots[i].is_none() && self.generations.is_usable(i))?;
|
||||
let id = NotificationId::from_raw(idx as u16);
|
||||
self.slots[idx] = Some(Notification::new(id, owner));
|
||||
Some(id)
|
||||
}
|
||||
|
||||
pub fn get(&self, id: NotificationId) -> Option<&Notification> {
|
||||
self.slots.get(id.raw() as usize)?.as_ref()
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, id: NotificationId) -> Option<&mut Notification> {
|
||||
self.slots.get_mut(id.raw() as usize)?.as_mut()
|
||||
}
|
||||
|
||||
pub fn generation(&self, id: NotificationId) -> Option<Generation> {
|
||||
self.get(id)?;
|
||||
self.generations.current(id.raw() as usize)
|
||||
}
|
||||
|
||||
pub fn revoke(&mut self, id: NotificationId) {
|
||||
if self.get(id).is_some() {
|
||||
self.generations.advance(id.raw() as usize);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn destroy(&mut self, id: NotificationId) -> Option<Notification> {
|
||||
let n = self.slots.get_mut(id.raw() as usize)?.take()?;
|
||||
self.generations.advance(id.raw() as usize);
|
||||
Some(n)
|
||||
}
|
||||
|
||||
pub fn ids_owned_by(&self, owner: ProcessId) -> impl Iterator<Item = NotificationId> + '_ {
|
||||
self.slots
|
||||
.iter()
|
||||
.flatten()
|
||||
.filter(move |n| n.owner == owner)
|
||||
.map(|n| n.id)
|
||||
}
|
||||
|
||||
pub fn remove_thread_from_all(&mut self, thread: ThreadId) {
|
||||
for n in self.slots.iter_mut().flatten() {
|
||||
n.remove_thread(thread);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for NotificationTable {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
43
crates/domain/src/notification/proofs.rs
Normal file
43
crates/domain/src/notification/proofs.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
//! Signals are never lost: every signal either wakes exactly one thread
|
||||
//! with its bits, or leaves those bits set for the next `Wait`.
|
||||
|
||||
use super::*;
|
||||
use types::badge::Badge;
|
||||
use types::id::{NotificationId, ProcessId, ThreadId};
|
||||
|
||||
#[kani::proof]
|
||||
#[kani::unwind(5)]
|
||||
fn a_signal_is_delivered_or_stored() {
|
||||
let mut n = Notification::new(NotificationId::from_raw(0), ProcessId::from_raw(0));
|
||||
n.bits = kani::any();
|
||||
if kani::any() {
|
||||
n.add_waiter(ThreadId::from_raw(kani::any::<u16>() % 4));
|
||||
}
|
||||
if kani::any() {
|
||||
n.bind(ThreadId::from_raw(kani::any::<u16>() % 4));
|
||||
}
|
||||
let before = n.bits;
|
||||
let badge = Badge::new(kani::any());
|
||||
let expected = if badge.is_none() { 1 } else { badge.raw() };
|
||||
|
||||
match n.signal(badge, kani::any()) {
|
||||
SignalEffect::WakeWaiter(_, bits) | SignalEffect::WakeBound(_, bits) => {
|
||||
assert!(bits == expected);
|
||||
assert!(n.bits == before, "woken signals are not also stored");
|
||||
}
|
||||
SignalEffect::Stored => {
|
||||
assert!(n.bits == before | expected);
|
||||
assert!(n.bits & expected == expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[kani::proof]
|
||||
#[kani::unwind(5)]
|
||||
fn take_bits_returns_everything_and_clears() {
|
||||
let mut n = Notification::new(NotificationId::from_raw(0), ProcessId::from_raw(0));
|
||||
let bits: usize = kani::any();
|
||||
n.bits = bits;
|
||||
assert!(n.take_bits() == bits);
|
||||
assert!(n.bits == 0);
|
||||
}
|
||||
116
crates/domain/src/notification/tests.rs
Normal file
116
crates/domain/src/notification/tests.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
use super::*;
|
||||
use types::badge::Badge;
|
||||
use types::id::{NotificationId, ProcessId, ThreadId};
|
||||
|
||||
fn ntfn() -> Notification {
|
||||
Notification::new(NotificationId::from_raw(0), ProcessId::from_raw(0))
|
||||
}
|
||||
|
||||
fn t(n: u16) -> ThreadId {
|
||||
ThreadId::from_raw(n)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signal_with_nobody_waiting_stores_the_badge() {
|
||||
let mut n = ntfn();
|
||||
assert_eq!(n.signal(Badge::new(0b100), false), SignalEffect::Stored);
|
||||
assert_eq!(n.signal(Badge::new(0b001), false), SignalEffect::Stored);
|
||||
assert_eq!(n.take_bits(), 0b101);
|
||||
assert_eq!(n.take_bits(), 0, "taking clears");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbadged_signal_sets_bit_zero() {
|
||||
let mut n = ntfn();
|
||||
n.signal(Badge::NONE, false);
|
||||
assert_eq!(n.take_bits(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signal_wakes_the_first_waiter_with_its_badge() {
|
||||
let mut n = ntfn();
|
||||
assert!(n.add_waiter(t(1)));
|
||||
assert!(n.add_waiter(t(2)));
|
||||
assert_eq!(
|
||||
n.signal(Badge::new(8), false),
|
||||
SignalEffect::WakeWaiter(t(1), 8)
|
||||
);
|
||||
assert_eq!(n.take_bits(), 0, "delivered bits are not also stored");
|
||||
assert_eq!(
|
||||
n.signal(Badge::new(4), false),
|
||||
SignalEffect::WakeWaiter(t(2), 4)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signal_wakes_a_receiving_bound_thread_when_no_waiter() {
|
||||
let mut n = ntfn();
|
||||
assert!(n.bind(t(5)));
|
||||
assert_eq!(
|
||||
n.signal(Badge::new(2), true),
|
||||
SignalEffect::WakeBound(t(5), 2)
|
||||
);
|
||||
assert_eq!(n.signal(Badge::new(2), false), SignalEffect::Stored);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn waiters_take_priority_over_the_bound_thread() {
|
||||
let mut n = ntfn();
|
||||
n.bind(t(5));
|
||||
n.add_waiter(t(1));
|
||||
assert_eq!(
|
||||
n.signal(Badge::new(2), true),
|
||||
SignalEffect::WakeWaiter(t(1), 2)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_one_thread_can_be_bound() {
|
||||
let mut n = ntfn();
|
||||
assert!(n.bind(t(1)));
|
||||
assert!(!n.bind(t(2)));
|
||||
n.unbind(t(1));
|
||||
assert!(n.bind(t(2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removed_waiter_is_not_woken() {
|
||||
let mut n = ntfn();
|
||||
n.add_waiter(t(1));
|
||||
n.remove_thread(t(1));
|
||||
assert_eq!(n.signal(Badge::new(1), false), SignalEffect::Stored);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_thread_also_unbinds() {
|
||||
let mut n = ntfn();
|
||||
n.bind(t(1));
|
||||
n.remove_thread(t(1));
|
||||
assert_eq!(n.bound(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_generations_follow_slot_reuse() {
|
||||
let mut table = NotificationTable::new();
|
||||
let owner = ProcessId::from_raw(3);
|
||||
let id = table.create(owner).unwrap();
|
||||
let g = table.generation(id).unwrap();
|
||||
assert!(table.destroy(id).is_some());
|
||||
assert_eq!(table.generation(id), None);
|
||||
let again = table.create(owner).unwrap();
|
||||
assert_eq!(again, id);
|
||||
assert!(!g.is_valid_against(&table.generation(again).unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_lists_notifications_by_owner() {
|
||||
let mut table = NotificationTable::new();
|
||||
let a = table.create(ProcessId::from_raw(1)).unwrap();
|
||||
table.create(ProcessId::from_raw(2)).unwrap();
|
||||
let mut n = 0;
|
||||
for id in table.ids_owned_by(ProcessId::from_raw(1)) {
|
||||
assert_eq!(id, a);
|
||||
n += 1;
|
||||
}
|
||||
assert_eq!(n, 1);
|
||||
}
|
||||
@@ -22,7 +22,7 @@ fn dead_thread_rejects_all_transitions() {
|
||||
|
||||
assert!(matches!(thread.state(), ThreadState::Dead));
|
||||
assert!(thread.set_running(CoreId::from_raw(0)).is_err());
|
||||
assert!(thread.block(BlockReason::WaitingAny).is_err());
|
||||
assert!(thread.block(BlockReason::AwaitingReply).is_err());
|
||||
assert!(thread.unblock().is_err());
|
||||
assert!(thread.preempt().is_err());
|
||||
assert!(thread.exit().is_err());
|
||||
@@ -36,7 +36,7 @@ fn only_running_thread_can_block() {
|
||||
let mut thread = test_thread(Priority::new(prio_val).unwrap());
|
||||
|
||||
assert!(matches!(thread.state(), ThreadState::Ready));
|
||||
assert!(thread.block(BlockReason::WaitingAny).is_err());
|
||||
assert!(thread.block(BlockReason::AwaitingReply).is_err());
|
||||
}
|
||||
|
||||
#[kani::proof]
|
||||
@@ -44,7 +44,7 @@ fn blocked_thread_cannot_run_directly() {
|
||||
let mut thread = test_thread(Priority::new(16).unwrap());
|
||||
|
||||
thread.set_running(CoreId::from_raw(0)).unwrap();
|
||||
thread.block(BlockReason::WaitingAny).unwrap();
|
||||
thread.block(BlockReason::AwaitingReply).unwrap();
|
||||
|
||||
assert!(thread.set_running(CoreId::from_raw(0)).is_err());
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use types::id::{EndpointId, IrqId, ProcessId, RegionId};
|
||||
use types::id::{EndpointId, IrqId, NotificationId, ProcessId, RegionId};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Resource {
|
||||
Memory(RegionId),
|
||||
Endpoint(EndpointId),
|
||||
Notification(NotificationId),
|
||||
Process(ProcessId),
|
||||
Interrupt(IrqId),
|
||||
ProcessCreate,
|
||||
|
||||
@@ -30,6 +30,13 @@ impl MultiCoreScheduler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether `thread` waits in some core's run queue.
|
||||
pub fn is_queued(&self, thread: ThreadId) -> bool {
|
||||
self.cores[..self.num_cores as usize]
|
||||
.iter()
|
||||
.any(|c| c.is_queued(thread))
|
||||
}
|
||||
|
||||
pub fn current(&self, core: CoreId) -> Option<ThreadId> {
|
||||
self.cores[core.raw() as usize].current()
|
||||
}
|
||||
|
||||
@@ -29,6 +29,10 @@ impl PerCoreScheduler {
|
||||
self.queue.add(thread, priority);
|
||||
}
|
||||
|
||||
pub fn is_queued(&self, thread: ThreadId) -> bool {
|
||||
self.queue.contains(thread)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, thread: ThreadId) {
|
||||
self.queue.remove(thread);
|
||||
}
|
||||
|
||||
@@ -1,42 +1,12 @@
|
||||
use types::{
|
||||
address::{DataRef, EntryPoint, StackPointer, VirtualAddress},
|
||||
badge::Badge,
|
||||
bounded::{CapSlot, PageCount},
|
||||
permission::PermissionSet,
|
||||
};
|
||||
|
||||
use crate::message::Message;
|
||||
|
||||
pub struct WaitSet {
|
||||
endpoints: [Option<CapSlot>; 4],
|
||||
interrupts: [Option<CapSlot>; 4],
|
||||
}
|
||||
|
||||
impl WaitSet {
|
||||
pub fn new(endpoints: [Option<CapSlot>; 4], interrupts: [Option<CapSlot>; 4]) -> Self {
|
||||
Self {
|
||||
endpoints,
|
||||
interrupts,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn endpoints(&self) -> &[Option<CapSlot>; 4] {
|
||||
&self.endpoints
|
||||
}
|
||||
|
||||
pub fn interrupts(&self) -> &[Option<CapSlot>; 4] {
|
||||
&self.interrupts
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.endpoints.iter().all(|s| s.is_none()) && self.interrupts.iter().all(|s| s.is_none())
|
||||
}
|
||||
}
|
||||
|
||||
pub enum WakeReason {
|
||||
MessageReady(CapSlot),
|
||||
InterruptFired(CapSlot),
|
||||
}
|
||||
|
||||
pub enum Syscall {
|
||||
Send {
|
||||
endpoint: CapSlot,
|
||||
@@ -50,15 +20,47 @@ pub enum Syscall {
|
||||
to_endpoint: CapSlot,
|
||||
permissions: PermissionSet,
|
||||
},
|
||||
WaitForInterrupt {
|
||||
/// Send, then block for the reply (docs/ipc.md).
|
||||
Call {
|
||||
endpoint: CapSlot,
|
||||
message: Message,
|
||||
},
|
||||
/// Answer the thread in the caller's reply slot.
|
||||
Reply {
|
||||
message: Message,
|
||||
},
|
||||
/// Reply (if a reply is pending), then receive.
|
||||
ReplyRecv {
|
||||
endpoint: CapSlot,
|
||||
message: Message,
|
||||
},
|
||||
/// Copy an unbadged endpoint/notification cap with a badge and
|
||||
/// narrower permissions into the caller's own table.
|
||||
Mint {
|
||||
slot: CapSlot,
|
||||
badge: Badge,
|
||||
permissions: PermissionSet,
|
||||
},
|
||||
CreateNotification,
|
||||
Signal {
|
||||
notification: CapSlot,
|
||||
},
|
||||
Wait {
|
||||
notification: CapSlot,
|
||||
},
|
||||
Poll {
|
||||
notification: CapSlot,
|
||||
},
|
||||
BindNotification {
|
||||
notification: CapSlot,
|
||||
},
|
||||
SetIrqNotification {
|
||||
irq: CapSlot,
|
||||
notification: CapSlot,
|
||||
},
|
||||
AcknowledgeInterrupt {
|
||||
irq: CapSlot,
|
||||
},
|
||||
WaitAny {
|
||||
sources: WaitSet,
|
||||
},
|
||||
CreateProcess {
|
||||
create_cap: CapSlot,
|
||||
image: DataRef,
|
||||
|
||||
@@ -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