feat(kernel): WatchDeath + SetTimer events as notification signals; adapter wakes at earliest deadline
This commit is contained in:
@@ -136,6 +136,14 @@ pub fn decode(nr: usize, a: &[usize; 6]) -> Option<Syscall> {
|
||||
SYS_REGION_ADDRESS => Syscall::RegionAddress {
|
||||
region: slot(a[0])?,
|
||||
},
|
||||
SYS_WATCH_DEATH => Syscall::WatchDeath {
|
||||
process: slot(a[0])?,
|
||||
notification: slot(a[1])?,
|
||||
},
|
||||
SYS_SET_TIMER => Syscall::SetTimer {
|
||||
notification: slot(a[0])?,
|
||||
deadline: types::time::Ticks::from_raw(a[1] as u64),
|
||||
},
|
||||
SYS_DEBUG_PUTCHAR => Syscall::DebugPutchar {
|
||||
console: slot(a[0])?,
|
||||
byte: a[1] as u8,
|
||||
|
||||
@@ -77,6 +77,9 @@ pub struct Kernel<P: Platform> {
|
||||
tree: domain::capability::tree::CapTree<{ captree::ALL_CAPS }>,
|
||||
/// Where each process's next heap search starts (see memory.rs).
|
||||
heap_cursors: [usize; MAX_PROCESSES],
|
||||
/// Who to signal when each process dies (see syscall/ipc/events.rs).
|
||||
death_watchers: [Option<domain::timer::Target>; MAX_PROCESSES],
|
||||
timers: domain::timer::Timers,
|
||||
}
|
||||
|
||||
impl<P: Platform> Kernel<P> {
|
||||
@@ -99,6 +102,8 @@ impl<P: Platform> Kernel<P> {
|
||||
homes: [types::id::CoreId::from_raw(0); MAX_THREADS],
|
||||
tree: domain::capability::tree::CapTree::new(),
|
||||
heap_cursors: [0; MAX_PROCESSES],
|
||||
death_watchers: [None; MAX_PROCESSES],
|
||||
timers: domain::timer::Timers::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +138,8 @@ impl<P: Platform> Kernel<P> {
|
||||
addr_of_mut!((*slot).homes).write([types::id::CoreId::from_raw(0); MAX_THREADS]);
|
||||
addr_of_mut!((*slot).tree).write(domain::capability::tree::CapTree::new());
|
||||
addr_of_mut!((*slot).heap_cursors).write([0; MAX_PROCESSES]);
|
||||
addr_of_mut!((*slot).death_watchers).write([None; MAX_PROCESSES]);
|
||||
addr_of_mut!((*slot).timers).write(domain::timer::Timers::new());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use types::{
|
||||
address::{EntryPoint, PhysicalAddress, StackPointer, VirtualAddress},
|
||||
id::{CoreId, IrqId, ProcessId},
|
||||
mapping::PagePerms,
|
||||
time::{Ticks, TimerDuration},
|
||||
time::Ticks,
|
||||
};
|
||||
|
||||
pub struct KaniPlatform;
|
||||
@@ -43,7 +43,7 @@ impl Platform for KaniPlatform {
|
||||
kani::assume(false);
|
||||
loop {}
|
||||
}
|
||||
fn set_timer(&mut self, _duration: TimerDuration) {}
|
||||
fn wake_at(&mut self, _deadline: Ticks) {}
|
||||
fn current_ticks(&self) -> Ticks {
|
||||
Ticks::from_raw(0)
|
||||
}
|
||||
|
||||
90
crates/kernel/src/syscall/ipc/events.rs
Normal file
90
crates/kernel/src/syscall/ipc/events.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
//! Events the kernel reports as notification signals: a watched process
|
||||
//! dying, a timer's deadline passing (see `domain::timer`).
|
||||
|
||||
use domain::{error::KernelError, resource::Resource, timer::Target};
|
||||
use platform::Platform;
|
||||
use types::{bounded::CapSlot, id::ProcessId, permission::Permission, time::Ticks};
|
||||
|
||||
use crate::Kernel;
|
||||
|
||||
impl<P: Platform> Kernel<P> {
|
||||
/// Signalling a notification is the Write right on it.
|
||||
fn target(&self, pid: ProcessId, slot: CapSlot) -> Result<Target, KernelError> {
|
||||
let (notification, badge, generation) =
|
||||
self.notification_cap(pid, slot, Permission::Write)?;
|
||||
Ok(Target {
|
||||
notification,
|
||||
generation,
|
||||
badge,
|
||||
})
|
||||
}
|
||||
|
||||
/// Signals `target` unless its notification has died since.
|
||||
fn fire(&mut self, target: Target) {
|
||||
if self.notifications.generation(target.notification) == Some(target.generation) {
|
||||
self.signal_notification(target.notification, target.badge);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn watch_death(
|
||||
&mut self,
|
||||
pid: ProcessId,
|
||||
process: CapSlot,
|
||||
notification: CapSlot,
|
||||
) -> Result<(), KernelError> {
|
||||
let watched = match self
|
||||
.check_cap(pid, process, Permission::Execute)?
|
||||
.resource()
|
||||
{
|
||||
Resource::Process(p) => *p,
|
||||
_ => return Err(KernelError::WrongObject),
|
||||
};
|
||||
let target = self.target(pid, notification)?;
|
||||
let slot = self
|
||||
.death_watchers
|
||||
.get_mut(watched.raw() as usize)
|
||||
.ok_or(KernelError::InvalidProcess(watched))?;
|
||||
*slot = Some(target);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A process is dying: tell its watcher, forget its own timer.
|
||||
pub(crate) fn report_death(&mut self, pid: ProcessId) {
|
||||
self.timers.clear(pid);
|
||||
if let Some(target) = self
|
||||
.death_watchers
|
||||
.get_mut(pid.raw() as usize)
|
||||
.and_then(Option::take)
|
||||
{
|
||||
self.fire(target);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_timer(
|
||||
&mut self,
|
||||
pid: ProcessId,
|
||||
notification: CapSlot,
|
||||
deadline: Ticks,
|
||||
) -> Result<(), KernelError> {
|
||||
let target = self.target(pid, notification)?;
|
||||
if deadline.raw() == 0 {
|
||||
self.timers.clear(pid);
|
||||
return Ok(());
|
||||
}
|
||||
self.timers.set(pid, deadline, target);
|
||||
self.fire_timers();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Signals every timer that is due and has this core woken for the next
|
||||
/// one. Call on every timer interrupt, whether or not a thread is running.
|
||||
pub fn fire_timers(&mut self) {
|
||||
let now = self.platform.current_ticks();
|
||||
while let Some(target) = self.timers.take_expired(now) {
|
||||
self.fire(target);
|
||||
}
|
||||
if let Some(next) = self.timers.earliest() {
|
||||
self.platform.wake_at(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Endpoint IPC, notifications and interrupt delivery (see docs/ipc.md).
|
||||
|
||||
mod endpoint;
|
||||
mod events;
|
||||
mod irq;
|
||||
mod notification;
|
||||
|
||||
|
||||
@@ -102,6 +102,20 @@ impl<P: Platform> Kernel<P> {
|
||||
self.region_address_cap(pid, region)
|
||||
.map(|pa| SyscallResult::Value(pa.raw())),
|
||||
),
|
||||
Syscall::WatchDeath {
|
||||
process,
|
||||
notification,
|
||||
} => complete(
|
||||
self.watch_death(pid, process, notification)
|
||||
.map(|()| SyscallResult::Ok),
|
||||
),
|
||||
Syscall::SetTimer {
|
||||
notification,
|
||||
deadline,
|
||||
} => complete(
|
||||
self.set_timer(pid, notification, deadline)
|
||||
.map(|()| SyscallResult::Ok),
|
||||
),
|
||||
Syscall::Delegate {
|
||||
slot,
|
||||
to_endpoint,
|
||||
|
||||
@@ -22,6 +22,7 @@ impl<P: Platform> Kernel<P> {
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
self.report_death(pid);
|
||||
self.cleanup_threads(&thread_ids);
|
||||
self.delete_all_caps(pid);
|
||||
self.destroy_endpoints_owned_by(pid);
|
||||
|
||||
@@ -42,6 +42,8 @@ fn every_defined_syscall_decodes() {
|
||||
SYS_CREATE_REGION,
|
||||
SYS_MAP_REGION,
|
||||
SYS_REGION_ADDRESS,
|
||||
SYS_WATCH_DEATH,
|
||||
SYS_SET_TIMER,
|
||||
];
|
||||
// slot 1, slot 2, small values, no cap
|
||||
let args = [1, 2, 1, 0, 0, NONE];
|
||||
@@ -52,7 +54,7 @@ fn every_defined_syscall_decodes() {
|
||||
|
||||
#[test]
|
||||
fn retired_and_unknown_numbers_do_not_decode() {
|
||||
for nr in [3, 5, 36, 1000, usize::MAX] {
|
||||
for nr in [3, 5, 38, 1000, usize::MAX] {
|
||||
assert!(decode(nr, &[0; 6]).is_none(), "{nr} decoded");
|
||||
}
|
||||
}
|
||||
|
||||
163
crates/kernel/src/tests/events.rs
Normal file
163
crates/kernel/src/tests/events.rs
Normal file
@@ -0,0 +1,163 @@
|
||||
//! Death watches and timers: the kernel's two events, both delivered as
|
||||
//! notification signals.
|
||||
|
||||
use super::helpers::CHILD_LIMITS;
|
||||
use super::*;
|
||||
use crate::{SyscallOutcome, SyscallResult};
|
||||
use domain::fault::Fault;
|
||||
use domain::resource::Resource;
|
||||
use domain::syscall::Syscall;
|
||||
use types::address::DataRef;
|
||||
use types::bounded::{CapSlot, Priority};
|
||||
use types::time::{Ticks, TimeSlice};
|
||||
|
||||
fn child(k: &mut Kernel<MockPlatform>) -> CapSlot {
|
||||
match k.handle_syscall(Syscall::CreateProcess {
|
||||
create_cap: CapSlot::new(0).unwrap(),
|
||||
image: DataRef::empty(),
|
||||
limits: CHILD_LIMITS,
|
||||
}) {
|
||||
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
||||
_ => panic!("create process"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Init with a child it watches, and the notification it watches with.
|
||||
fn watching() -> (Kernel<MockPlatform>, ProcessId, CapSlot, CapSlot) {
|
||||
let (mut k, init) = kernel_with_process();
|
||||
let proc_cap = child(&mut k);
|
||||
let ntfn = k.create_notification_cap(init).unwrap();
|
||||
k.watch_death(init, proc_cap, ntfn).unwrap();
|
||||
(k, init, proc_cap, ntfn)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_watcher_hears_a_kill() {
|
||||
let (mut k, init, proc_cap, ntfn) = watching();
|
||||
assert_eq!(k.poll(init, ntfn).unwrap(), 0);
|
||||
k.handle_syscall(Syscall::Kill { process: proc_cap });
|
||||
assert_eq!(k.poll(init, ntfn).unwrap(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_watcher_hears_an_exit_or_a_fault() {
|
||||
let (mut k, init, proc_cap, ntfn) = watching();
|
||||
let pid = k.process_of_cap_for_test(proc_cap);
|
||||
k.destroy_process(pid);
|
||||
assert_eq!(k.poll(init, ntfn).unwrap(), 1);
|
||||
|
||||
let proc_cap = child(&mut k);
|
||||
k.watch_death(init, proc_cap, ntfn).unwrap();
|
||||
let pid = k.process_of_cap_for_test(proc_cap);
|
||||
let tid = k.spawn_thread_for_test(pid, Priority::DEFAULT, TimeSlice::DEFAULT);
|
||||
k.handle_fault(tid, Fault::IllegalInstruction, 0);
|
||||
assert_eq!(k.poll(init, ntfn).unwrap(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn watching_needs_a_process_cap_and_a_notification_to_signal() {
|
||||
let (mut k, init) = kernel_with_process();
|
||||
let proc_cap = child(&mut k);
|
||||
let ntfn = k.create_notification_cap(init).unwrap();
|
||||
assert!(k.watch_death(init, ntfn, ntfn).is_err());
|
||||
assert!(k.watch_death(init, proc_cap, proc_cap).is_err());
|
||||
k.handle_syscall(Syscall::Kill { process: proc_cap });
|
||||
assert_eq!(k.poll(init, ntfn).unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_dead_notification_is_never_signalled() {
|
||||
let (mut k, init) = kernel_with_process();
|
||||
let watched = child(&mut k);
|
||||
let watcher = child(&mut k);
|
||||
let watcher_pid = k.process_of_cap_for_test(watcher);
|
||||
// The watcher's own notification, which dies with it.
|
||||
let ntfn = k.create_notification_cap(watcher_pid).unwrap();
|
||||
let theirs = watched_in(&mut k, watched, watcher_pid);
|
||||
k.watch_death(watcher_pid, theirs, ntfn).unwrap();
|
||||
k.destroy_process(watcher_pid);
|
||||
// A new notification may now reuse the id; the old watch must not hit it.
|
||||
let fresh = k.create_notification_cap(init).unwrap();
|
||||
k.handle_syscall(Syscall::Kill { process: watched });
|
||||
assert_eq!(k.poll(init, fresh).unwrap(), 0);
|
||||
}
|
||||
|
||||
/// A copy of init's `slot` in `to`'s table.
|
||||
fn watched_in(k: &mut Kernel<MockPlatform>, slot: CapSlot, to: ProcessId) -> CapSlot {
|
||||
let pid = k.process_of_cap_for_test(slot);
|
||||
k.grant_cap_for_test(
|
||||
to,
|
||||
Resource::Process(pid),
|
||||
types::permission::PermissionSet::empty().with(types::permission::Permission::Execute),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_timer_fires_at_its_deadline_and_not_before() {
|
||||
let (mut k, init) = kernel_with_process();
|
||||
let ntfn = k.create_notification_cap(init).unwrap();
|
||||
k.platform_mut().set_ticks(Ticks::from_raw(10));
|
||||
k.set_timer(init, ntfn, Ticks::from_raw(100)).unwrap();
|
||||
assert_eq!(k.platform_mut().wake_at, Some(Ticks::from_raw(100)));
|
||||
k.platform_mut().set_ticks(Ticks::from_raw(99));
|
||||
k.fire_timers();
|
||||
assert_eq!(k.poll(init, ntfn).unwrap(), 0);
|
||||
k.platform_mut().set_ticks(Ticks::from_raw(100));
|
||||
k.fire_timers();
|
||||
assert_eq!(k.poll(init, ntfn).unwrap(), 1);
|
||||
// Once only.
|
||||
k.fire_timers();
|
||||
assert_eq!(k.poll(init, ntfn).unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_past_deadline_fires_at_once_and_zero_cancels() {
|
||||
let (mut k, init) = kernel_with_process();
|
||||
let ntfn = k.create_notification_cap(init).unwrap();
|
||||
k.platform_mut().set_ticks(Ticks::from_raw(50));
|
||||
k.set_timer(init, ntfn, Ticks::from_raw(20)).unwrap();
|
||||
assert_eq!(k.poll(init, ntfn).unwrap(), 1);
|
||||
k.set_timer(init, ntfn, Ticks::from_raw(60)).unwrap();
|
||||
k.set_timer(init, ntfn, Ticks::from_raw(0)).unwrap();
|
||||
k.platform_mut().set_ticks(Ticks::from_raw(70));
|
||||
k.fire_timers();
|
||||
assert_eq!(k.poll(init, ntfn).unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_timer_dies_with_its_process() {
|
||||
let (mut k, init) = kernel_with_process();
|
||||
let ntfn = k.create_notification_cap(init).unwrap();
|
||||
let proc_cap = child(&mut k);
|
||||
let pid = k.process_of_cap_for_test(proc_cap);
|
||||
let theirs = k.grant_cap_for_test(
|
||||
pid,
|
||||
Resource::Notification(notification_id(&k, init, ntfn)),
|
||||
types::permission::PermissionSet::empty().with(types::permission::Permission::Write),
|
||||
);
|
||||
k.platform_mut().set_ticks(Ticks::from_raw(0));
|
||||
k.set_timer(pid, theirs, Ticks::from_raw(10)).unwrap();
|
||||
k.destroy_process(pid);
|
||||
k.platform_mut().set_ticks(Ticks::from_raw(20));
|
||||
k.fire_timers();
|
||||
assert_eq!(k.poll(init, ntfn).unwrap(), 0);
|
||||
}
|
||||
|
||||
fn notification_id(
|
||||
k: &Kernel<MockPlatform>,
|
||||
pid: ProcessId,
|
||||
slot: CapSlot,
|
||||
) -> types::id::NotificationId {
|
||||
match k
|
||||
.processes
|
||||
.get(pid)
|
||||
.unwrap()
|
||||
.capabilities()
|
||||
.get(slot)
|
||||
.unwrap()
|
||||
.resource()
|
||||
{
|
||||
Resource::Notification(id) => *id,
|
||||
_ => panic!("not a notification"),
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use types::{
|
||||
address::{EntryPoint, PhysicalAddress, StackPointer, VirtualAddress},
|
||||
id::{CoreId, IrqId, ProcessId},
|
||||
mapping::PagePerms,
|
||||
time::{Ticks, TimerDuration},
|
||||
time::Ticks,
|
||||
};
|
||||
|
||||
const MAX_SPACES: usize = 64;
|
||||
@@ -31,6 +31,8 @@ pub struct MockPlatform {
|
||||
lies: BTreeMap<(u16, usize), usize>,
|
||||
/// Cores an IPI was sent to and that have not taken it yet.
|
||||
ipis: [bool; 8],
|
||||
/// The last deadline the kernel asked to be woken at.
|
||||
pub wake_at: Option<Ticks>,
|
||||
}
|
||||
|
||||
impl MockPlatform {
|
||||
@@ -45,6 +47,7 @@ impl MockPlatform {
|
||||
zeroed: BTreeSet::new(),
|
||||
lies: BTreeMap::new(),
|
||||
ipis: [false; 8],
|
||||
wake_at: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +175,9 @@ impl Platform for MockPlatform {
|
||||
fn enable_user_mode(&mut self, _entry: EntryPoint, _stack: StackPointer) -> ! {
|
||||
panic!("enable_user_mode is not supported in tests")
|
||||
}
|
||||
fn set_timer(&mut self, _duration: TimerDuration) {}
|
||||
fn wake_at(&mut self, deadline: Ticks) {
|
||||
self.wake_at = Some(deadline);
|
||||
}
|
||||
fn current_ticks(&self) -> Ticks {
|
||||
self.ticks
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ mod board;
|
||||
mod budget;
|
||||
mod capability;
|
||||
mod captree_model;
|
||||
mod events;
|
||||
mod exit;
|
||||
mod invariants;
|
||||
mod ipc;
|
||||
|
||||
Reference in New Issue
Block a user