feat(kernel): WatchDeath + SetTimer events as notification signals; adapter wakes at earliest deadline

This commit is contained in:
2026-10-27 16:06:04 +01:00
parent a4f467f45d
commit 817a38993b
25 changed files with 517 additions and 21 deletions

View File

@@ -1,4 +1,10 @@
use core::sync::atomic::{AtomicU64, Ordering};
use crate::board; use crate::board;
use crate::smp::{MAX_HARTS, hart_id};
/// When each hart's timer is set to go off next.
static NEXT: [AtomicU64; MAX_HARTS] = [const { AtomicU64::new(u64::MAX) }; MAX_HARTS];
pub fn read_time() -> u64 { pub fn read_time() -> u64 {
let time: u64; let time: u64;
@@ -21,15 +27,27 @@ fn sbi_set_timer(stime: u64) {
} }
pub fn init() { pub fn init() {
let now = read_time(); set_next(read_time() + board::TICK_INTERVAL);
sbi_set_timer(now + board::TICK_INTERVAL);
} }
pub fn set_next(deadline: u64) { pub fn set_next(deadline: u64) {
if let Some(next) = NEXT.get(hart_id()) {
next.store(deadline, Ordering::Relaxed);
}
sbi_set_timer(deadline); sbi_set_timer(deadline);
} }
pub fn handle_interrupt() { /// Goes off at `deadline` too, if that is before the next tick.
let now = read_time(); pub fn wake_at(deadline: u64) {
sbi_set_timer(now + board::TICK_INTERVAL); if NEXT
.get(hart_id())
.is_some_and(|n| deadline < n.load(Ordering::Relaxed))
{
set_next(deadline);
}
}
/// Re-arms the tick; the kernel then re-arms for its earliest timer.
pub fn handle_interrupt() {
set_next(read_time() + board::TICK_INTERVAL);
} }

View File

@@ -4,10 +4,9 @@ use types::{
address::{EntryPoint, PhysicalAddress, StackPointer, VirtualAddress}, address::{EntryPoint, PhysicalAddress, StackPointer, VirtualAddress},
id::{CoreId, IrqId, ProcessId}, id::{CoreId, IrqId, ProcessId},
mapping::PagePerms, mapping::PagePerms,
time::{Ticks, TimerDuration}, time::Ticks,
}; };
use crate::board;
use crate::hw::{plic::Plic, timer}; use crate::hw::{plic::Plic, timer};
use crate::mmu; use crate::mmu;
use crate::smp; use crate::smp;
@@ -136,9 +135,8 @@ impl Platform for RiscVPlatform {
} }
} }
fn set_timer(&mut self, _duration: TimerDuration) { fn wake_at(&mut self, deadline: Ticks) {
let now = timer::read_time(); timer::wake_at(deadline.raw());
timer::set_next(now + board::TICK_INTERVAL);
} }
fn current_ticks(&self) -> Ticks { fn current_ticks(&self) -> Ticks {

View File

@@ -104,6 +104,7 @@ fn handle_interrupt(frame: &mut TrapFrame, cause: usize) {
CAUSE_S_TIMER => { CAUSE_S_TIMER => {
crate::hw::timer::handle_interrupt(); crate::hw::timer::handle_interrupt();
let kern = kern(); let kern = kern();
kern.fire_timers();
if let Some(current_tid) = kern.current_thread() { if let Some(current_tid) = kern.current_thread() {
let decision = kern.handle_tick(this_core()); let decision = kern.handle_tick(this_core());
if let domain::scheduler::SchedulerDecision::Preempt(next) = decision { if let domain::scheduler::SchedulerDecision::Preempt(next) = decision {

View File

@@ -70,6 +70,7 @@ fn next_runnable() -> types::id::ThreadId {
if sip & (1 << CAUSE_S_TIMER) != 0 { if sip & (1 << CAUSE_S_TIMER) != 0 {
// No thread is running, so there is no time slice to charge. // No thread is running, so there is no time slice to charge.
crate::hw::timer::handle_interrupt(); crate::hw::timer::handle_interrupt();
kern().fire_timers();
} }
if sip & (1 << CAUSE_S_EXTERNAL) != 0 { if sip & (1 << CAUSE_S_EXTERNAL) != 0 {
dispatch_external(); dispatch_external();

View File

@@ -32,3 +32,4 @@ pub mod resource;
pub mod scheduler; pub mod scheduler;
pub mod syscall; pub mod syscall;
pub mod thread; pub mod thread;
pub mod timer;

View File

@@ -4,6 +4,7 @@ use types::{
bounded::{CapSlot, PageCount}, bounded::{CapSlot, PageCount},
mapping::PagePerms, mapping::PagePerms,
permission::PermissionSet, permission::PermissionSet,
time::Ticks,
}; };
use crate::message::Message; use crate::message::Message;
@@ -91,6 +92,18 @@ pub enum Syscall {
RegionAddress { RegionAddress {
region: CapSlot, region: CapSlot,
}, },
/// Signals `notification` when the process behind `process` dies, by
/// exit, fault or kill. One watcher per process; a new one replaces it.
WatchDeath {
process: CapSlot,
notification: CapSlot,
},
/// Signals `notification` once `GetTime` reaches `deadline`; replaces
/// the caller's timer, and a zero deadline cancels it.
SetTimer {
notification: CapSlot,
deadline: Ticks,
},
/// Removes a capability from the caller's own table. /// Removes a capability from the caller's own table.
DropCap { DropCap {
slot: CapSlot, slot: CapSlot,

View File

@@ -0,0 +1,67 @@
//! Events the kernel reports by signalling a notification: a process's
//! death, a deadline passing. One timer per process.
use types::{badge::Badge, generation::Generation, id::NotificationId, id::ProcessId, time::Ticks};
use crate::process::MAX_PROCESSES;
#[cfg(test)]
#[path = "timer_tests.rs"]
mod tests;
/// A notification as a capability named it; once the notification is
/// destroyed (its generation moves on) the target is dead.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Target {
pub notification: NotificationId,
pub generation: Generation,
pub badge: Badge,
}
pub struct Timers {
deadlines: [Option<(Ticks, Target)>; MAX_PROCESSES],
}
impl Timers {
pub const fn new() -> Self {
Self {
deadlines: [None; MAX_PROCESSES],
}
}
/// Replaces `owner`'s timer.
pub fn set(&mut self, owner: ProcessId, deadline: Ticks, target: Target) -> bool {
match self.deadlines.get_mut(owner.raw() as usize) {
Some(slot) => {
*slot = Some((deadline, target));
true
}
None => false,
}
}
pub fn clear(&mut self, owner: ProcessId) {
if let Some(slot) = self.deadlines.get_mut(owner.raw() as usize) {
*slot = None;
}
}
/// Removes and returns one timer whose deadline is at or before `now`.
pub fn take_expired(&mut self, now: Ticks) -> Option<Target> {
let slot = self
.deadlines
.iter_mut()
.find(|s| s.is_some_and(|(d, _)| d <= now))?;
slot.take().map(|(_, t)| t)
}
pub fn earliest(&self) -> Option<Ticks> {
self.deadlines.iter().flatten().map(|(d, _)| *d).min()
}
}
impl Default for Timers {
fn default() -> Self {
Self::new()
}
}

View File

@@ -0,0 +1,37 @@
use super::{Target, Timers};
use types::{badge::Badge, generation::Generation, id::NotificationId, id::ProcessId, time::Ticks};
fn target(n: u16) -> Target {
Target {
notification: NotificationId::from_raw(n),
generation: Generation::initial(),
badge: Badge::NONE,
}
}
fn pid(n: u16) -> ProcessId {
ProcessId::from_raw(n)
}
#[test]
fn only_expired_timers_fire_and_each_fires_once() {
let mut t = Timers::new();
t.set(pid(0), Ticks::from_raw(10), target(0));
t.set(pid(1), Ticks::from_raw(30), target(1));
assert_eq!(t.earliest(), Some(Ticks::from_raw(10)));
assert_eq!(t.take_expired(Ticks::from_raw(9)), None);
assert_eq!(t.take_expired(Ticks::from_raw(10)), Some(target(0)));
assert_eq!(t.take_expired(Ticks::from_raw(29)), None);
assert_eq!(t.earliest(), Some(Ticks::from_raw(30)));
}
#[test]
fn setting_again_replaces_and_clear_cancels() {
let mut t = Timers::new();
t.set(pid(2), Ticks::from_raw(10), target(0));
t.set(pid(2), Ticks::from_raw(50), target(1));
assert_eq!(t.take_expired(Ticks::from_raw(20)), None);
t.clear(pid(2));
assert_eq!(t.earliest(), None);
assert!(!t.set(pid(500), Ticks::from_raw(1), target(0)));
}

View File

@@ -71,8 +71,12 @@ fn run_phases(storage: &mut Storage, scratch: &mut [u8]) -> Result<(), &'static
syscall::map_region(page, VirtualAddress::from_raw(OBSERVE_VA), PagePerms::ReadWrite) syscall::map_region(page, VirtualAddress::from_raw(OBSERVE_VA), PagePerms::ReadWrite)
.map_err(|_| "cannot map the counter page")?; .map_err(|_| "cannot map the counter page")?;
let events = syscall::create_notification().map_err(|_| "no notification")?;
timer_is_on_time(events)?;
// Revoke must unmap the page on every hart; the spinner then faults and dies. // Revoke must unmap the page on every hart; the spinner then faults and dies.
let spinner = start_spinner(storage, scratch, page)?; let spinner = start_spinner(storage, scratch, page)?;
syscall::watch_death(spinner, events).map_err(|_| "cannot watch the spinner")?;
if !syscall::revoke(page) { if !syscall::revoke(page) {
return Err("revoke failed"); return Err("revoke failed");
} }
@@ -80,18 +84,43 @@ fn run_phases(storage: &mut Storage, scratch: &mut [u8]) -> Result<(), &'static
if syscall::kill(spinner) { if syscall::kill(spinner) {
return Err("the spinner survived losing its page"); return Err("the spinner survived losing its page");
} }
if syscall::poll(events) != Ok(1) {
return Err("nobody heard the spinner fault");
}
libkos::println!("smptest: revoke stops every hart: ok"); libkos::println!("smptest: revoke stops every hart: ok");
// Kill must stop threads running on other harts before it returns. // Kill must stop threads running on other harts before it returns.
let spinner = start_spinner(storage, scratch, page)?; let spinner = start_spinner(storage, scratch, page)?;
syscall::watch_death(spinner, events).map_err(|_| "cannot watch the spinner")?;
if !syscall::kill(spinner) { if !syscall::kill(spinner) {
return Err("kill failed"); return Err("kill failed");
} }
check_stopped("a counter moved after its process was killed")?; check_stopped("a counter moved after its process was killed")?;
if syscall::poll(events) != Ok(1) {
return Err("nobody heard the spinner killed");
}
libkos::println!("smptest: kill stops every hart: ok"); libkos::println!("smptest: kill stops every hart: ok");
Ok(()) Ok(())
} }
/// A timer must wake its waiter at the deadline, not at the next scheduling
/// tick (a second away).
fn timer_is_on_time(events: CapSlot) -> Result<(), &'static str> {
let start = syscall::get_time().raw();
let deadline = start + 50 * TICKS_PER_MS;
syscall::set_timer(events, types::time::Ticks::from_raw(deadline)).map_err(|_| "set_timer failed")?;
syscall::wait(events).map_err(|_| "wait failed")?;
let woke = syscall::get_time().raw();
if woke < deadline {
return Err("the timer fired early");
}
if woke - deadline > 20 * TICKS_PER_MS {
return Err("the timer fired late");
}
libkos::println!("smptest: timer wakes on time: ok");
Ok(())
}
/// Runs the test, reports, and halts the machine. /// Runs the test, reports, and halts the machine.
pub fn run(storage: &mut Storage, scratch: &mut [u8]) -> ! { pub fn run(storage: &mut Storage, scratch: &mut [u8]) -> ! {
match run_phases(storage, scratch) { match run_phases(storage, scratch) {

View File

@@ -136,6 +136,14 @@ pub fn decode(nr: usize, a: &[usize; 6]) -> Option<Syscall> {
SYS_REGION_ADDRESS => Syscall::RegionAddress { SYS_REGION_ADDRESS => Syscall::RegionAddress {
region: slot(a[0])?, 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 { SYS_DEBUG_PUTCHAR => Syscall::DebugPutchar {
console: slot(a[0])?, console: slot(a[0])?,
byte: a[1] as u8, byte: a[1] as u8,

View File

@@ -77,6 +77,9 @@ pub struct Kernel<P: Platform> {
tree: domain::capability::tree::CapTree<{ captree::ALL_CAPS }>, tree: domain::capability::tree::CapTree<{ captree::ALL_CAPS }>,
/// Where each process's next heap search starts (see memory.rs). /// Where each process's next heap search starts (see memory.rs).
heap_cursors: [usize; MAX_PROCESSES], 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> { impl<P: Platform> Kernel<P> {
@@ -99,6 +102,8 @@ impl<P: Platform> Kernel<P> {
homes: [types::id::CoreId::from_raw(0); MAX_THREADS], homes: [types::id::CoreId::from_raw(0); MAX_THREADS],
tree: domain::capability::tree::CapTree::new(), tree: domain::capability::tree::CapTree::new(),
heap_cursors: [0; MAX_PROCESSES], 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).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).tree).write(domain::capability::tree::CapTree::new());
addr_of_mut!((*slot).heap_cursors).write([0; MAX_PROCESSES]); 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());
} }
} }

View File

@@ -6,7 +6,7 @@ use types::{
address::{EntryPoint, PhysicalAddress, StackPointer, VirtualAddress}, address::{EntryPoint, PhysicalAddress, StackPointer, VirtualAddress},
id::{CoreId, IrqId, ProcessId}, id::{CoreId, IrqId, ProcessId},
mapping::PagePerms, mapping::PagePerms,
time::{Ticks, TimerDuration}, time::Ticks,
}; };
pub struct KaniPlatform; pub struct KaniPlatform;
@@ -43,7 +43,7 @@ impl Platform for KaniPlatform {
kani::assume(false); kani::assume(false);
loop {} loop {}
} }
fn set_timer(&mut self, _duration: TimerDuration) {} fn wake_at(&mut self, _deadline: Ticks) {}
fn current_ticks(&self) -> Ticks { fn current_ticks(&self) -> Ticks {
Ticks::from_raw(0) Ticks::from_raw(0)
} }

View 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);
}
}
}

View File

@@ -1,6 +1,7 @@
//! Endpoint IPC, notifications and interrupt delivery (see docs/ipc.md). //! Endpoint IPC, notifications and interrupt delivery (see docs/ipc.md).
mod endpoint; mod endpoint;
mod events;
mod irq; mod irq;
mod notification; mod notification;

View File

@@ -102,6 +102,20 @@ impl<P: Platform> Kernel<P> {
self.region_address_cap(pid, region) self.region_address_cap(pid, region)
.map(|pa| SyscallResult::Value(pa.raw())), .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 { Syscall::Delegate {
slot, slot,
to_endpoint, to_endpoint,

View File

@@ -22,6 +22,7 @@ impl<P: Platform> Kernel<P> {
Err(_) => return, Err(_) => return,
}; };
self.report_death(pid);
self.cleanup_threads(&thread_ids); self.cleanup_threads(&thread_ids);
self.delete_all_caps(pid); self.delete_all_caps(pid);
self.destroy_endpoints_owned_by(pid); self.destroy_endpoints_owned_by(pid);

View File

@@ -42,6 +42,8 @@ fn every_defined_syscall_decodes() {
SYS_CREATE_REGION, SYS_CREATE_REGION,
SYS_MAP_REGION, SYS_MAP_REGION,
SYS_REGION_ADDRESS, SYS_REGION_ADDRESS,
SYS_WATCH_DEATH,
SYS_SET_TIMER,
]; ];
// slot 1, slot 2, small values, no cap // slot 1, slot 2, small values, no cap
let args = [1, 2, 1, 0, 0, NONE]; let args = [1, 2, 1, 0, 0, NONE];
@@ -52,7 +54,7 @@ fn every_defined_syscall_decodes() {
#[test] #[test]
fn retired_and_unknown_numbers_do_not_decode() { 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"); assert!(decode(nr, &[0; 6]).is_none(), "{nr} decoded");
} }
} }

View 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"),
}
}

View File

@@ -6,7 +6,7 @@ use types::{
address::{EntryPoint, PhysicalAddress, StackPointer, VirtualAddress}, address::{EntryPoint, PhysicalAddress, StackPointer, VirtualAddress},
id::{CoreId, IrqId, ProcessId}, id::{CoreId, IrqId, ProcessId},
mapping::PagePerms, mapping::PagePerms,
time::{Ticks, TimerDuration}, time::Ticks,
}; };
const MAX_SPACES: usize = 64; const MAX_SPACES: usize = 64;
@@ -31,6 +31,8 @@ pub struct MockPlatform {
lies: BTreeMap<(u16, usize), usize>, lies: BTreeMap<(u16, usize), usize>,
/// Cores an IPI was sent to and that have not taken it yet. /// Cores an IPI was sent to and that have not taken it yet.
ipis: [bool; 8], ipis: [bool; 8],
/// The last deadline the kernel asked to be woken at.
pub wake_at: Option<Ticks>,
} }
impl MockPlatform { impl MockPlatform {
@@ -45,6 +47,7 @@ impl MockPlatform {
zeroed: BTreeSet::new(), zeroed: BTreeSet::new(),
lies: BTreeMap::new(), lies: BTreeMap::new(),
ipis: [false; 8], ipis: [false; 8],
wake_at: None,
} }
} }
@@ -172,7 +175,9 @@ impl Platform for MockPlatform {
fn enable_user_mode(&mut self, _entry: EntryPoint, _stack: StackPointer) -> ! { fn enable_user_mode(&mut self, _entry: EntryPoint, _stack: StackPointer) -> ! {
panic!("enable_user_mode is not supported in tests") 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 { fn current_ticks(&self) -> Ticks {
self.ticks self.ticks
} }

View File

@@ -7,6 +7,7 @@ mod board;
mod budget; mod budget;
mod capability; mod capability;
mod captree_model; mod captree_model;
mod events;
mod exit; mod exit;
mod invariants; mod invariants;
mod ipc; mod ipc;

View File

@@ -56,3 +56,22 @@ pub fn ack_interrupt(irq: CapSlot) -> Result<()> {
) )
.map(|_| ()) .map(|_| ())
} }
/// Signals `notification` when the process behind `process` dies.
pub fn watch_death(process: CapSlot, notification: CapSlot) -> Result<()> {
raw(
SYS_WATCH_DEATH,
[process.value() as usize, notification.value() as usize, 0, 0, 0, 0],
)
.map(|_| ())
}
/// Signals `notification` once `get_time()` reaches `deadline`; replaces
/// this process's timer. A zero deadline cancels it.
pub fn set_timer(notification: CapSlot, deadline: types::time::Ticks) -> Result<()> {
raw(
SYS_SET_TIMER,
[notification.value() as usize, deadline.raw() as usize, 0, 0, 0, 0],
)
.map(|_| ())
}

View File

@@ -4,7 +4,7 @@ use types::{
address::{EntryPoint, PhysicalAddress, StackPointer, VirtualAddress}, address::{EntryPoint, PhysicalAddress, StackPointer, VirtualAddress},
id::{CoreId, IrqId, ProcessId}, id::{CoreId, IrqId, ProcessId},
mapping::PagePerms, mapping::PagePerms,
time::{Ticks, TimerDuration}, time::Ticks,
}; };
/// Supplies frames for page tables while the platform builds an address /// Supplies frames for page tables while the platform builds an address
@@ -54,7 +54,9 @@ pub trait Platform {
fn zero_frame(&mut self, pa: PhysicalAddress); fn zero_frame(&mut self, pa: PhysicalAddress);
fn enable_user_mode(&mut self, entry: EntryPoint, stack: StackPointer) -> !; fn enable_user_mode(&mut self, entry: EntryPoint, stack: StackPointer) -> !;
fn set_timer(&mut self, duration: TimerDuration); /// Makes sure this core takes a timer interrupt by `deadline`, on top
/// of its scheduling tick.
fn wake_at(&mut self, deadline: Ticks);
fn current_ticks(&self) -> Ticks; fn current_ticks(&self) -> Ticks;
fn mask_irq(&mut self, irq: IrqId); fn mask_irq(&mut self, irq: IrqId);
fn unmask_irq(&mut self, irq: IrqId); fn unmask_irq(&mut self, irq: IrqId);

View File

@@ -41,6 +41,8 @@ pub const SYS_SET_IRQ_NOTIFICATION: usize = 32;
pub const SYS_CREATE_REGION: usize = 33; pub const SYS_CREATE_REGION: usize = 33;
pub const SYS_MAP_REGION: usize = 34; pub const SYS_MAP_REGION: usize = 34;
pub const SYS_REGION_ADDRESS: usize = 35; pub const SYS_REGION_ADDRESS: usize = 35;
pub const SYS_WATCH_DEATH: usize = 36;
pub const SYS_SET_TIMER: usize = 37;
/// `PagePerms` in a register. /// `PagePerms` in a register.
pub const PERMS_READ_ONLY: usize = 0; pub const PERMS_READ_ONLY: usize = 0;

View File

@@ -15,9 +15,6 @@ impl Ticks {
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TimerDuration(u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TimeSlice { pub struct TimeSlice {
remaining: u32, remaining: u32,

View File

@@ -185,7 +185,26 @@ pass to that parent, so their budgets return there in turn.
Destroying an endpoint or notification wakes every thread blocked on it with Destroying an endpoint or notification wakes every thread blocked on it with
`InvalidCap`. Destroying a thread removes it from every queue, binding and `InvalidCap`. Destroying a thread removes it from every queue, binding and
reply slot; a dying process loses every IRQ route it set. reply slot; a dying process loses every IRQ route it set and its timer.
## Events
Two things the kernel notices itself, and reports by signalling a
notification (with that notification capability's badge, or bit 0 if it
has none). Both need `Write` on the notification, the right to signal it.
- `WatchDeath(process, notification)`: needs `Execute` on the process
capability. The notification is signalled when that process dies, whether
by `Exit`, a fault or `Kill`. One watcher per process; a new one replaces
the old. Badge the notification per child to tell children apart.
- `SetTimer(notification, deadline)`: signals once `GetTime` reaches
`deadline` (absolute, in `GetTime` ticks). One timer per process; setting
it again replaces it, a zero deadline cancels it, a past one fires at once.
The adapter programs the hardware for the earliest deadline, so a timer
fires on time rather than on the next scheduling tick.
A target whose notification has since been destroyed is never signalled,
even if its id was reused.
## Register ABI (RISC-V) ## Register ABI (RISC-V)