feat(ipc): saved replies: SaveReply/ReplySaved (8 per thread, released on thread death, unforgeable); terminal READ saved until Enter (line.rs editor, reader queue), now pinged
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
use types::{
|
use types::{
|
||||||
address::{DataRef, EntryPoint, StackPointer, VirtualAddress},
|
address::{DataRef, EntryPoint, StackPointer, VirtualAddress},
|
||||||
badge::Badge,
|
badge::Badge,
|
||||||
bounded::{CapSlot, PageCount},
|
bounded::{CapSlot, PageCount, SavedReply},
|
||||||
mapping::PagePerms,
|
mapping::PagePerms,
|
||||||
permission::PermissionSet,
|
permission::PermissionSet,
|
||||||
time::Ticks,
|
time::Ticks,
|
||||||
@@ -104,6 +104,14 @@ pub enum Syscall {
|
|||||||
notification: CapSlot,
|
notification: CapSlot,
|
||||||
deadline: Ticks,
|
deadline: Ticks,
|
||||||
},
|
},
|
||||||
|
/// Keeps the caller this thread owes a reply to, to answer later
|
||||||
|
/// (`ReplySaved`) while it receives others; returns which it is.
|
||||||
|
SaveReply,
|
||||||
|
/// Answers a caller kept with `SaveReply`, once.
|
||||||
|
ReplySaved {
|
||||||
|
reply: SavedReply,
|
||||||
|
message: Message,
|
||||||
|
},
|
||||||
/// Removes a capability from the caller's own table.
|
/// Removes a capability from the caller's own table.
|
||||||
DropCap {
|
DropCap {
|
||||||
slot: CapSlot,
|
slot: CapSlot,
|
||||||
|
|||||||
@@ -107,8 +107,8 @@ pub const SERVERS: &[Program] = &[
|
|||||||
..server("display", b"bin/display", Limits { pages: 1280, regions: 4, ..SMALL })
|
..server("display", b"bin/display", Limits { pages: 1280, regions: 4, ..SMALL })
|
||||||
},
|
},
|
||||||
// Text on the display, lines from the keyboard (it takes input focus).
|
// Text on the display, lines from the keyboard (it takes input focus).
|
||||||
// Its clients wait on lines and Ctrl+C; `READ` waits for the user.
|
// Its clients wait on lines and Ctrl+C.
|
||||||
Program { uses: &["display"], restarts_clients: true, pinged: false, ..server("terminal", b"bin/terminal", SMALL) },
|
Program { uses: &["display"], restarts_clients: true, ..server("terminal", b"bin/terminal", SMALL) },
|
||||||
server("session", b"bin/session", SMALL),
|
server("session", b"bin/session", SMALL),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use domain::{message::Message, syscall::Syscall};
|
|||||||
use types::{
|
use types::{
|
||||||
address::{DataRef, EntryPoint, StackPointer, VirtualAddress},
|
address::{DataRef, EntryPoint, StackPointer, VirtualAddress},
|
||||||
badge::Badge,
|
badge::Badge,
|
||||||
bounded::{CapSlot, PageCount},
|
bounded::{CapSlot, PageCount, SavedReply},
|
||||||
permission::PermissionSet,
|
permission::PermissionSet,
|
||||||
syscall_abi::*,
|
syscall_abi::*,
|
||||||
};
|
};
|
||||||
@@ -151,6 +151,11 @@ pub fn decode(nr: usize, a: &[usize; 6]) -> Option<Syscall> {
|
|||||||
notification: slot(a[0])?,
|
notification: slot(a[0])?,
|
||||||
deadline: types::time::Ticks::from_raw(a[1] as u64),
|
deadline: types::time::Ticks::from_raw(a[1] as u64),
|
||||||
},
|
},
|
||||||
|
SYS_SAVE_REPLY => Syscall::SaveReply,
|
||||||
|
SYS_REPLY_SAVED => Syscall::ReplySaved {
|
||||||
|
reply: SavedReply::new(u8::try_from(a[0]).ok()?)?,
|
||||||
|
message: message(a)?,
|
||||||
|
},
|
||||||
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,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use types::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use super::super::{Delivery, DeliveryKind, SyscallOutcome, SyscallResult};
|
use super::super::{Delivery, DeliveryKind, SyscallOutcome, SyscallResult};
|
||||||
use super::Outgoing;
|
use super::{Outgoing, ThreadRef};
|
||||||
use crate::Kernel;
|
use crate::Kernel;
|
||||||
|
|
||||||
impl<P: Platform> Kernel<P> {
|
impl<P: Platform> Kernel<P> {
|
||||||
@@ -226,11 +226,17 @@ impl<P: Platform> Kernel<P> {
|
|||||||
.ipc_mut(tid)
|
.ipc_mut(tid)
|
||||||
.and_then(|s| s.reply_to.take())
|
.and_then(|s| s.reply_to.take())
|
||||||
.ok_or(KernelError::NoReply)?;
|
.ok_or(KernelError::NoReply)?;
|
||||||
|
self.answer(pid, caller, message);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wakes `caller` with `message` from `pid`, if it is still waiting.
|
||||||
|
pub(super) fn answer(&mut self, pid: ProcessId, caller: ThreadRef, message: Message) {
|
||||||
let waiting = self.is_live(caller)
|
let waiting = self.is_live(caller)
|
||||||
&& self.is_blocked_as(caller.tid, |r| *r == BlockReason::AwaitingReply);
|
&& self.is_blocked_as(caller.tid, |r| *r == BlockReason::AwaitingReply);
|
||||||
if !waiting {
|
if !waiting {
|
||||||
// The caller died; the reply has nowhere to go.
|
// The caller died; the reply has nowhere to go.
|
||||||
return Ok(());
|
return;
|
||||||
}
|
}
|
||||||
let to = self.thread_process(caller.tid);
|
let to = self.thread_process(caller.tid);
|
||||||
// A reply may always carry a capability the server can delegate.
|
// A reply may always carry a capability the server can delegate.
|
||||||
@@ -242,7 +248,6 @@ impl<P: Platform> Kernel<P> {
|
|||||||
cap,
|
cap,
|
||||||
};
|
};
|
||||||
self.wake(caller.tid, SyscallResult::Received(delivery));
|
self.wake(caller.tid, SyscallResult::Received(delivery));
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn reply_recv(
|
pub(crate) fn reply_recv(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ mod endpoint;
|
|||||||
mod events;
|
mod events;
|
||||||
mod irq;
|
mod irq;
|
||||||
mod notification;
|
mod notification;
|
||||||
|
mod saved;
|
||||||
|
|
||||||
use domain::{
|
use domain::{
|
||||||
error::KernelError,
|
error::KernelError,
|
||||||
@@ -44,13 +45,19 @@ pub struct IpcState {
|
|||||||
pub(crate) outgoing: Option<Outgoing>,
|
pub(crate) outgoing: Option<Outgoing>,
|
||||||
/// The caller this thread owes a reply to.
|
/// The caller this thread owes a reply to.
|
||||||
pub(crate) reply_to: Option<ThreadRef>,
|
pub(crate) reply_to: Option<ThreadRef>,
|
||||||
|
/// Callers it kept to answer later (`SaveReply`).
|
||||||
|
pub(crate) saved: [Option<ThreadRef>; SAVED],
|
||||||
pub(crate) bound: Option<NotificationId>,
|
pub(crate) bound: Option<NotificationId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Callers one thread can keep waiting.
|
||||||
|
pub(crate) const SAVED: usize = types::bounded::SavedReply::MAX as usize + 1;
|
||||||
|
|
||||||
impl IpcState {
|
impl IpcState {
|
||||||
pub const EMPTY: Self = Self {
|
pub const EMPTY: Self = Self {
|
||||||
outgoing: None,
|
outgoing: None,
|
||||||
reply_to: None,
|
reply_to: None,
|
||||||
|
saved: [None; SAVED],
|
||||||
bound: None,
|
bound: None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
52
crates/kernel/src/syscall/ipc/saved.rs
Normal file
52
crates/kernel/src/syscall/ipc/saved.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
//! Replies kept for later (docs/ipc.md): a server that cannot answer a call
|
||||||
|
//! yet (it waits on the user, or on another server) keeps the caller and
|
||||||
|
//! receives others meanwhile. The kept caller is named only within the
|
||||||
|
//! thread that kept it, so it can be neither passed on nor forged, and it
|
||||||
|
//! is released with an error when that thread dies.
|
||||||
|
|
||||||
|
use domain::{error::KernelError, message::Message};
|
||||||
|
use platform::Platform;
|
||||||
|
use types::{
|
||||||
|
bounded::SavedReply,
|
||||||
|
id::{ProcessId, ThreadId},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::Kernel;
|
||||||
|
|
||||||
|
impl<P: Platform> Kernel<P> {
|
||||||
|
/// Moves the caller `tid` owes a reply to into its first free slot.
|
||||||
|
pub(crate) fn save_reply(&mut self, tid: ThreadId) -> Result<SavedReply, KernelError> {
|
||||||
|
let s = self.ipc_mut(tid).ok_or(KernelError::NoReply)?;
|
||||||
|
let caller = s.reply_to.ok_or(KernelError::NoReply)?;
|
||||||
|
let (index, free) = s
|
||||||
|
.saved
|
||||||
|
.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, c)| c.is_none())
|
||||||
|
.ok_or(KernelError::NoFreeSlot)?;
|
||||||
|
let reply = u8::try_from(index)
|
||||||
|
.ok()
|
||||||
|
.and_then(SavedReply::new)
|
||||||
|
.ok_or(KernelError::NoFreeSlot)?;
|
||||||
|
*free = Some(caller);
|
||||||
|
s.reply_to = None;
|
||||||
|
Ok(reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Answers the caller kept as `reply`; it is forgotten either way.
|
||||||
|
pub(crate) fn reply_saved(
|
||||||
|
&mut self,
|
||||||
|
tid: ThreadId,
|
||||||
|
pid: ProcessId,
|
||||||
|
reply: SavedReply,
|
||||||
|
message: Message,
|
||||||
|
) -> Result<(), KernelError> {
|
||||||
|
let caller = self
|
||||||
|
.ipc_mut(tid)
|
||||||
|
.and_then(|s| s.saved.get_mut(usize::from(reply.value())))
|
||||||
|
.and_then(Option::take)
|
||||||
|
.ok_or(KernelError::NoReply)?;
|
||||||
|
self.answer(pid, caller, message);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -138,6 +138,14 @@ impl<P: Platform> Kernel<P> {
|
|||||||
Syscall::ReplyRecv { endpoint, message } => {
|
Syscall::ReplyRecv { endpoint, message } => {
|
||||||
self.reply_recv(tid, pid, endpoint, message)
|
self.reply_recv(tid, pid, endpoint, message)
|
||||||
}
|
}
|
||||||
|
Syscall::SaveReply => complete(
|
||||||
|
self.save_reply(tid)
|
||||||
|
.map(|r| SyscallResult::Value(usize::from(r.value()))),
|
||||||
|
),
|
||||||
|
Syscall::ReplySaved { reply, message } => complete(
|
||||||
|
self.reply_saved(tid, pid, reply, message)
|
||||||
|
.map(|()| SyscallResult::Ok),
|
||||||
|
),
|
||||||
Syscall::CreateNotification => {
|
Syscall::CreateNotification => {
|
||||||
complete(self.create_notification_cap(pid).map(SyscallResult::Slot))
|
complete(self.create_notification_cap(pid).map(SyscallResult::Slot))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,11 @@ impl<P: Platform> Kernel<P> {
|
|||||||
n.remove_thread(*tid);
|
n.remove_thread(*tid);
|
||||||
}
|
}
|
||||||
let ipc = self.ipc_mut(*tid).map(core::mem::take).unwrap_or_default();
|
let ipc = self.ipc_mut(*tid).map(core::mem::take).unwrap_or_default();
|
||||||
if let Some(caller) = ipc.reply_to {
|
for caller in ipc
|
||||||
|
.reply_to
|
||||||
|
.into_iter()
|
||||||
|
.chain(ipc.saved.into_iter().flatten())
|
||||||
|
{
|
||||||
self.release_caller(caller);
|
self.release_caller(caller);
|
||||||
}
|
}
|
||||||
self.evict(*tid);
|
self.evict(*tid);
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ fn every_defined_syscall_decodes() {
|
|||||||
SYS_REGION_ADDRESS,
|
SYS_REGION_ADDRESS,
|
||||||
SYS_WATCH_DEATH,
|
SYS_WATCH_DEATH,
|
||||||
SYS_SET_TIMER,
|
SYS_SET_TIMER,
|
||||||
|
SYS_SAVE_REPLY,
|
||||||
|
SYS_REPLY_SAVED,
|
||||||
];
|
];
|
||||||
// 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];
|
||||||
@@ -61,6 +63,11 @@ fn retired_and_unknown_numbers_do_not_decode() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn out_of_range_arguments_do_not_decode() {
|
fn out_of_range_arguments_do_not_decode() {
|
||||||
|
assert!(
|
||||||
|
decode(SYS_REPLY_SAVED, &[8, 0, 0, 0, 0, NONE]).is_none(),
|
||||||
|
"a thread keeps 8 replies"
|
||||||
|
);
|
||||||
|
assert!(decode(SYS_REPLY_SAVED, &[7, 0, 0, 0, 0, NONE]).is_some());
|
||||||
assert!(decode(SYS_RECEIVE, &[64, 0, 0, 0, 0, 0]).is_none());
|
assert!(decode(SYS_RECEIVE, &[64, 0, 0, 0, 0, 0]).is_none());
|
||||||
assert!(
|
assert!(
|
||||||
decode(SYS_SEND, &[1, 0, 0, 0, 0, 64]).is_none(),
|
decode(SYS_SEND, &[1, 0, 0, 0, 0, 64]).is_none(),
|
||||||
|
|||||||
@@ -110,6 +110,9 @@ fn error(outcome: &SyscallOutcome) -> Option<&KernelError> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[path = "ipc_saved.rs"]
|
||||||
|
mod saved;
|
||||||
|
|
||||||
// ---- rendezvous ----
|
// ---- rendezvous ----
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
192
crates/kernel/src/tests/ipc_saved.rs
Normal file
192
crates/kernel/src/tests/ipc_saved.rs
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
//! Replies kept for later: `SaveReply` and `ReplySaved`.
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use types::bounded::SavedReply;
|
||||||
|
|
||||||
|
/// A call from `client` that `server` receives and keeps; which one.
|
||||||
|
fn call_and_save(t: &mut Ipc, client: ThreadId, word: usize) -> SavedReply {
|
||||||
|
let (tb, a_send, b_recv) = (t.tb, t.a_send, t.b_recv);
|
||||||
|
t.run(
|
||||||
|
client,
|
||||||
|
Syscall::Call {
|
||||||
|
endpoint: a_send,
|
||||||
|
message: msg([word, 0, 0, 0]),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
t.run(tb, Syscall::Receive { endpoint: b_recv });
|
||||||
|
match t.run(tb, Syscall::SaveReply) {
|
||||||
|
SyscallOutcome::Complete(SyscallResult::Value(v)) => SavedReply::new(v as u8).unwrap(),
|
||||||
|
_ => panic!("not saved"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reply_saved(t: &mut Ipc, reply: SavedReply, word: usize) -> SyscallOutcome {
|
||||||
|
let tb = t.tb;
|
||||||
|
t.run(
|
||||||
|
tb,
|
||||||
|
Syscall::ReplySaved {
|
||||||
|
reply,
|
||||||
|
message: msg([word, 0, 0, 0]),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn client(t: &mut Ipc) -> ThreadId {
|
||||||
|
t.k.spawn_thread_for_test(t.a, Priority::DEFAULT, TimeSlice::DEFAULT)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_saved_caller_is_answered_after_others_are_served() {
|
||||||
|
let mut t = Ipc::new();
|
||||||
|
let (ta, tb, b_recv) = (t.ta, t.tb, t.b_recv);
|
||||||
|
let first = call_and_save(&mut t, ta, 1);
|
||||||
|
// The server goes on: the next caller is received and answered first.
|
||||||
|
let other = client(&mut t);
|
||||||
|
let a_send = t.a_send;
|
||||||
|
t.run(
|
||||||
|
other,
|
||||||
|
Syscall::Call {
|
||||||
|
endpoint: a_send,
|
||||||
|
message: msg([2, 0, 0, 0]),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let d = delivered(&t.run(tb, Syscall::Receive { endpoint: b_recv })).unwrap();
|
||||||
|
assert_eq!(d.words[0], 2);
|
||||||
|
assert!(
|
||||||
|
matches!(t.state(ta), Some(ThreadState::Blocked(_))),
|
||||||
|
"the saved caller still waits"
|
||||||
|
);
|
||||||
|
t.run(
|
||||||
|
tb,
|
||||||
|
Syscall::Reply {
|
||||||
|
message: msg([20, 0, 0, 0]),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert_eq!(pending_delivery(t.woken_with(other)).unwrap().words[0], 20);
|
||||||
|
assert!(matches!(
|
||||||
|
reply_saved(&mut t, first, 10),
|
||||||
|
SyscallOutcome::Complete(SyscallResult::Ok)
|
||||||
|
));
|
||||||
|
assert!(matches!(t.state(ta), Some(ThreadState::Ready)));
|
||||||
|
assert_eq!(pending_delivery(t.woken_with(ta)).unwrap().words[0], 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nothing_to_save_without_a_pending_call() {
|
||||||
|
let mut t = Ipc::new();
|
||||||
|
let tb = t.tb;
|
||||||
|
assert!(matches!(
|
||||||
|
error(&t.run(tb, Syscall::SaveReply)),
|
||||||
|
Some(KernelError::NoReply)
|
||||||
|
));
|
||||||
|
// Saving takes it: there is no reply pending any more.
|
||||||
|
let ta = t.ta;
|
||||||
|
call_and_save(&mut t, ta, 0);
|
||||||
|
assert!(matches!(
|
||||||
|
error(&t.run(
|
||||||
|
tb,
|
||||||
|
Syscall::Reply {
|
||||||
|
message: msg([0; 4])
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
Some(KernelError::NoReply)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_saved_reply_is_answered_once() {
|
||||||
|
let mut t = Ipc::new();
|
||||||
|
let ta = t.ta;
|
||||||
|
let r = call_and_save(&mut t, ta, 0);
|
||||||
|
assert!(error(&reply_saved(&mut t, r, 1)).is_none());
|
||||||
|
assert!(matches!(
|
||||||
|
error(&reply_saved(&mut t, r, 2)),
|
||||||
|
Some(KernelError::NoReply)
|
||||||
|
));
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
error(&reply_saved(&mut t, SavedReply::new(5).unwrap(), 2)),
|
||||||
|
Some(KernelError::NoReply)
|
||||||
|
),
|
||||||
|
"never saved"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_receive_does_not_release_saved_callers() {
|
||||||
|
let mut t = Ipc::new();
|
||||||
|
let (ta, tb, b_recv) = (t.ta, t.tb, t.b_recv);
|
||||||
|
call_and_save(&mut t, ta, 0);
|
||||||
|
// Nothing queued: the server just waits, the saved caller with it.
|
||||||
|
assert!(blocked(&t.run(tb, Syscall::Receive { endpoint: b_recv })));
|
||||||
|
assert!(matches!(t.state(ta), Some(ThreadState::Blocked(_))));
|
||||||
|
assert!(t.woken_with(ta).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_thread_keeps_eight_callers_at_most() {
|
||||||
|
let mut t = Ipc::new();
|
||||||
|
let (tb, a_send, b_recv) = (t.tb, t.a_send, t.b_recv);
|
||||||
|
let ta = t.ta;
|
||||||
|
call_and_save(&mut t, ta, 0);
|
||||||
|
for i in 1..=usize::from(SavedReply::MAX) {
|
||||||
|
let c = client(&mut t);
|
||||||
|
assert_eq!(usize::from(call_and_save(&mut t, c, i).value()), i);
|
||||||
|
}
|
||||||
|
let late = client(&mut t);
|
||||||
|
t.run(
|
||||||
|
late,
|
||||||
|
Syscall::Call {
|
||||||
|
endpoint: a_send,
|
||||||
|
message: msg([9, 0, 0, 0]),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
t.run(tb, Syscall::Receive { endpoint: b_recv });
|
||||||
|
assert!(matches!(
|
||||||
|
error(&t.run(tb, Syscall::SaveReply)),
|
||||||
|
Some(KernelError::NoFreeSlot)
|
||||||
|
));
|
||||||
|
// Still the pending reply: it can be answered the usual way.
|
||||||
|
t.run(
|
||||||
|
tb,
|
||||||
|
Syscall::Reply {
|
||||||
|
message: msg([90, 0, 0, 0]),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert_eq!(pending_delivery(t.woken_with(late)).unwrap().words[0], 90);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn server_death_releases_its_saved_callers() {
|
||||||
|
let mut t = Ipc::new();
|
||||||
|
let (ta, b) = (t.ta, t.b);
|
||||||
|
call_and_save(&mut t, ta, 0);
|
||||||
|
let other = client(&mut t);
|
||||||
|
call_and_save(&mut t, other, 1);
|
||||||
|
t.k.destroy_process(b);
|
||||||
|
for c in [ta, other] {
|
||||||
|
assert!(matches!(t.state(c), Some(ThreadState::Ready)));
|
||||||
|
assert!(matches!(
|
||||||
|
t.woken_with(c),
|
||||||
|
Some(SyscallResult::Error(KernelError::NoReply))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_saved_reply_to_a_caller_that_died_goes_nowhere() {
|
||||||
|
let mut t = Ipc::new();
|
||||||
|
let other = client(&mut t);
|
||||||
|
let r = call_and_save(&mut t, other, 0);
|
||||||
|
// Killed while waiting for the reply (its process holds others).
|
||||||
|
t.k.cleanup_threads(&[Some(other)]);
|
||||||
|
let reborn = client(&mut t);
|
||||||
|
assert!(
|
||||||
|
error(&reply_saved(&mut t, r, 7)).is_none(),
|
||||||
|
"answering a dead caller is not the server's error"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
t.woken_with(reborn).is_none(),
|
||||||
|
"and a thread that reused its id hears nothing"
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -86,6 +86,19 @@ pub fn reply(words: [usize; 4], cap: Option<Give>) -> Result<()> {
|
|||||||
raw(SYS_REPLY, message_regs(0, words, cap)).map(|_| ())
|
raw(SYS_REPLY, message_regs(0, words, cap)).map(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Keeps the caller this thread owes a reply to, to answer later with
|
||||||
|
/// `reply_saved` while it receives others; the reply is not pending any
|
||||||
|
/// more, so nothing answers it in the meantime.
|
||||||
|
pub fn save_reply() -> Result<types::bounded::SavedReply> {
|
||||||
|
let r = raw(SYS_SAVE_REPLY, [0; 6])?;
|
||||||
|
u8::try_from(r[1]).ok().and_then(types::bounded::SavedReply::new).ok_or(Error::InvalidArgument)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Answers a caller kept with `save_reply`.
|
||||||
|
pub fn reply_saved(reply: types::bounded::SavedReply, words: [usize; 4], cap: Option<Give>) -> Result<()> {
|
||||||
|
raw(SYS_REPLY_SAVED, message_regs(usize::from(reply.value()), words, cap)).map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
/// Replies to the pending `Call` (if any), then receives the next message.
|
/// Replies to the pending `Call` (if any), then receives the next message.
|
||||||
pub fn reply_recv(endpoint: CapSlot, words: [usize; 4], cap: Option<Give>) -> Result<Received> {
|
pub fn reply_recv(endpoint: CapSlot, words: [usize; 4], cap: Option<Give>) -> Result<Received> {
|
||||||
raw(
|
raw(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
||||||
pub mod grid;
|
pub mod grid;
|
||||||
|
pub mod line;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -77,15 +78,6 @@ impl server::Console for Screen {
|
|||||||
let _ = syscall::set_timer(self.timer, types::time::Ticks::from_raw(at));
|
let _ = syscall::set_timer(self.timer, types::time::Ticks::from_raw(at));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_key(&mut self) -> u8 {
|
|
||||||
loop {
|
|
||||||
if let Some(key) = self.poll_key() {
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
let _ = syscall::wait(self.notify);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_key(&mut self) -> Option<u8> {
|
fn poll_key(&mut self) -> Option<u8> {
|
||||||
while let Some(event) = display::poll_input(DISPLAY) {
|
while let Some(event) = display::poll_input(DISPLAY) {
|
||||||
if let Some(key) = byte(event) {
|
if let Some(key) = byte(event) {
|
||||||
@@ -95,6 +87,14 @@ impl server::Console for Screen {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn save_reply(&mut self) -> Option<types::bounded::SavedReply> {
|
||||||
|
syscall::save_reply().ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn answer(&mut self, reply: types::bounded::SavedReply, words: [usize; 4]) {
|
||||||
|
let _ = syscall::reply_saved(reply, words, None);
|
||||||
|
}
|
||||||
|
|
||||||
fn take_focus(&mut self) {
|
fn take_focus(&mut self) {
|
||||||
display::subscribe(DISPLAY, self.notify, Badge::new(KEYS_BIT));
|
display::subscribe(DISPLAY, self.notify, Badge::new(KEYS_BIT));
|
||||||
display::clear(DISPLAY, BG);
|
display::clear(DISPLAY, BG);
|
||||||
|
|||||||
94
crates/servers/terminal-server/src/line.rs
Normal file
94
crates/servers/terminal-server/src/line.rs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
//! The line being typed: edited key by key and echoed into the grid (as
|
||||||
|
//! `*` if it is secret) until Enter; Ctrl+C drops it, and it reads as an
|
||||||
|
//! empty line. Once done it is read out in text-message chunks; a secret
|
||||||
|
//! line is forgotten as soon as it is.
|
||||||
|
|
||||||
|
use types::log::{TEXT_BYTES, text_words};
|
||||||
|
|
||||||
|
use crate::grid::Grid;
|
||||||
|
use crate::server::BACKSPACE;
|
||||||
|
|
||||||
|
const MAX_LINE: usize = 256;
|
||||||
|
|
||||||
|
pub struct Line {
|
||||||
|
bytes: [u8; MAX_LINE],
|
||||||
|
len: usize,
|
||||||
|
/// How much of a finished line has been read.
|
||||||
|
read: usize,
|
||||||
|
/// Being typed, and whether it is secret.
|
||||||
|
typing: Option<bool>,
|
||||||
|
secret: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Line {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self { bytes: [0; MAX_LINE], len: 0, read: 0, typing: None, secret: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(&mut self, secret: bool) {
|
||||||
|
self.len = 0;
|
||||||
|
self.read = 0;
|
||||||
|
self.typing = Some(secret);
|
||||||
|
self.secret = secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn typing(&self) -> bool {
|
||||||
|
self.typing.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finished, and not all read yet.
|
||||||
|
pub fn unread(&self) -> bool {
|
||||||
|
self.typing.is_none() && self.read < self.len
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One key of the line; true once it is done.
|
||||||
|
pub fn key(&mut self, key: u8, grid: &mut Grid) -> bool {
|
||||||
|
let Some(secret) = self.typing else { return false };
|
||||||
|
match key {
|
||||||
|
types::terminal::CTRL_C => {
|
||||||
|
self.bytes[0] = b'\n';
|
||||||
|
self.len = 1;
|
||||||
|
for &b in b"^C\n" {
|
||||||
|
grid.put(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b'\n' => {
|
||||||
|
self.bytes[self.len] = b'\n';
|
||||||
|
self.len += 1;
|
||||||
|
grid.put(b'\n');
|
||||||
|
}
|
||||||
|
BACKSPACE if self.len > 0 => {
|
||||||
|
self.len -= 1;
|
||||||
|
grid.back();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Room is kept for the closing newline.
|
||||||
|
b if self.len + 1 < MAX_LINE && (b' '..=b'~').contains(&b) => {
|
||||||
|
self.bytes[self.len] = b;
|
||||||
|
self.len += 1;
|
||||||
|
grid.put(if secret { b'*' } else { b });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
self.typing = None;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The next chunk of a finished line.
|
||||||
|
pub fn chunk(&mut self) -> [usize; 4] {
|
||||||
|
let end = (self.read + TEXT_BYTES).min(self.len);
|
||||||
|
let words = text_words(&self.bytes[self.read..end]);
|
||||||
|
self.read = end;
|
||||||
|
if self.secret && self.read == self.len {
|
||||||
|
self.bytes.fill(0);
|
||||||
|
}
|
||||||
|
words
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Line {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,11 @@
|
|||||||
//! behind `Console`.
|
//! behind `Console`.
|
||||||
|
|
||||||
use server_framework::{Encode, MALFORMED, Server};
|
use server_framework::{Encode, MALFORMED, Server};
|
||||||
use types::bounded::CapSlot;
|
use types::bounded::{CapSlot, SavedReply};
|
||||||
use types::log::{TEXT_BYTES, text_of, text_words};
|
use types::log::{text_of, text_words};
|
||||||
|
|
||||||
use crate::grid::Grid;
|
use crate::grid::Grid;
|
||||||
|
use crate::line::Line;
|
||||||
|
|
||||||
/// What the terminal draws on and reads from.
|
/// What the terminal draws on and reads from.
|
||||||
pub trait Console {
|
pub trait Console {
|
||||||
@@ -17,10 +18,12 @@ pub trait Console {
|
|||||||
fn scroll(&mut self, rows: usize);
|
fn scroll(&mut self, rows: usize);
|
||||||
/// Soon calls `TerminalServer::signal`, to flush a burst of output once.
|
/// Soon calls `TerminalServer::signal`, to flush a burst of output once.
|
||||||
fn flush_later(&mut self);
|
fn flush_later(&mut self);
|
||||||
/// Blocks until a key is typed: a byte, `\n` for Enter, 8 for Backspace.
|
/// A key already typed, without waiting: a byte, `\n` for Enter, 8
|
||||||
fn next_key(&mut self) -> u8;
|
/// for Backspace.
|
||||||
/// A key already typed, without waiting.
|
|
||||||
fn poll_key(&mut self) -> Option<u8>;
|
fn poll_key(&mut self) -> Option<u8>;
|
||||||
|
/// Keeps the caller of the request being handled, to answer later.
|
||||||
|
fn save_reply(&mut self) -> Option<SavedReply>;
|
||||||
|
fn answer(&mut self, reply: SavedReply, words: [usize; 4]);
|
||||||
/// Takes input focus back from whoever took it, and clears the screen.
|
/// Takes input focus back from whoever took it, and clears the screen.
|
||||||
fn take_focus(&mut self);
|
fn take_focus(&mut self);
|
||||||
fn interrupt(&mut self, notification: CapSlot);
|
fn interrupt(&mut self, notification: CapSlot);
|
||||||
@@ -29,17 +32,19 @@ pub trait Console {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub const BACKSPACE: u8 = 8;
|
pub const BACKSPACE: u8 = 8;
|
||||||
const MAX_LINE: usize = 256;
|
|
||||||
/// Keys typed while no line is being read, kept for the next one.
|
/// Keys typed while no line is being read, kept for the next one.
|
||||||
const TYPEAHEAD: usize = 64;
|
const TYPEAHEAD: usize = 64;
|
||||||
|
/// Callers waiting for a line at once, at most.
|
||||||
|
const READERS: usize = 4;
|
||||||
|
|
||||||
pub struct TerminalServer<C: Console> {
|
pub struct TerminalServer<C: Console> {
|
||||||
console: C,
|
console: C,
|
||||||
grid: Grid,
|
grid: Grid,
|
||||||
/// A typed line not yet handed out in full, and how much of it was.
|
/// A typed line not yet handed out in full, and how much of it was.
|
||||||
line: [u8; MAX_LINE],
|
line: Line,
|
||||||
line_len: usize,
|
/// Callers waiting for a line, oldest first, and whether theirs is
|
||||||
line_read: usize,
|
/// secret: the oldest gets the line being typed.
|
||||||
|
waiting: [Option<(SavedReply, bool)>; READERS],
|
||||||
flush_pending: bool,
|
flush_pending: bool,
|
||||||
/// How many cells of each row are drawn on the screen.
|
/// How many cells of each row are drawn on the screen.
|
||||||
drawn: [usize; crate::grid::MAX_ROWS],
|
drawn: [usize; crate::grid::MAX_ROWS],
|
||||||
@@ -63,9 +68,8 @@ impl<C: Console> TerminalServer<C> {
|
|||||||
let mut s = Self {
|
let mut s = Self {
|
||||||
console,
|
console,
|
||||||
grid: Grid::new(cols, rows),
|
grid: Grid::new(cols, rows),
|
||||||
line: [0; MAX_LINE],
|
line: Line::new(),
|
||||||
line_len: 0,
|
waiting: [None; READERS],
|
||||||
line_read: 0,
|
|
||||||
flush_pending: false,
|
flush_pending: false,
|
||||||
drawn: [0; crate::grid::MAX_ROWS],
|
drawn: [0; crate::grid::MAX_ROWS],
|
||||||
typeahead: [0; TYPEAHEAD],
|
typeahead: [0; TYPEAHEAD],
|
||||||
@@ -131,20 +135,26 @@ impl<C: Console> TerminalServer<C> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A key typed ahead, or the next one.
|
/// A key typed ahead, or one just typed.
|
||||||
fn next_key(&mut self) -> u8 {
|
fn take_key(&mut self) -> Option<u8> {
|
||||||
if self.typeahead_len == 0 {
|
if self.typeahead_len == 0 {
|
||||||
return self.console.next_key();
|
return self.console.poll_key();
|
||||||
}
|
}
|
||||||
let key = self.typeahead[0];
|
let key = self.typeahead[0];
|
||||||
self.typeahead.copy_within(1..self.typeahead_len, 0);
|
self.typeahead.copy_within(1..self.typeahead_len, 0);
|
||||||
self.typeahead_len -= 1;
|
self.typeahead_len -= 1;
|
||||||
key
|
Some(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Keys typed while nobody reads a line: Ctrl+C interrupts, the rest
|
/// Keys typed while nobody reads a line: Ctrl+C interrupts, the rest
|
||||||
/// wait for the next line.
|
/// wait for the next line.
|
||||||
fn keys_arrived(&mut self) {
|
fn keys_arrived(&mut self) {
|
||||||
|
if self.line.typing() {
|
||||||
|
if self.type_keys() {
|
||||||
|
self.serve_waiting();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
while let Some(key) = self.console.poll_key() {
|
while let Some(key) = self.console.poll_key() {
|
||||||
if key == types::terminal::CTRL_C {
|
if key == types::terminal::CTRL_C {
|
||||||
self.typeahead_len = 0;
|
self.typeahead_len = 0;
|
||||||
@@ -159,63 +169,73 @@ impl<C: Console> TerminalServer<C> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Edits a line from the keyboard, echoing it (as `*` if `secret`),
|
fn begin_line(&mut self, secret: bool) {
|
||||||
/// until Enter. Ctrl+C drops it and reads as an empty line.
|
|
||||||
fn read_line(&mut self, secret: bool) {
|
|
||||||
self.console.flush();
|
self.console.flush();
|
||||||
// Flushed now; a flush timer still running is harmless.
|
// Flushed now; a flush timer still running is harmless.
|
||||||
self.flush_pending = false;
|
self.flush_pending = false;
|
||||||
self.line_len = 0;
|
self.line.start(secret);
|
||||||
self.line_read = 0;
|
}
|
||||||
loop {
|
|
||||||
match self.next_key() {
|
/// Feeds the keys there are into the line; whether it is done.
|
||||||
types::terminal::CTRL_C => {
|
fn type_keys(&mut self) -> bool {
|
||||||
self.line[0] = b'\n';
|
let mut done = false;
|
||||||
self.line_len = 1;
|
while !done && let Some(key) = self.take_key() {
|
||||||
for &b in b"^C\n" {
|
done = self.line.key(key, &mut self.grid);
|
||||||
self.grid.put(b);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
b'\n' => {
|
|
||||||
self.line[self.line_len] = b'\n';
|
|
||||||
self.line_len += 1;
|
|
||||||
self.grid.put(b'\n');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
BACKSPACE if self.line_len > 0 => {
|
|
||||||
self.line_len -= 1;
|
|
||||||
self.grid.back();
|
|
||||||
}
|
|
||||||
BACKSPACE => continue,
|
|
||||||
// Room is kept for the closing newline.
|
|
||||||
b if self.line_len + 1 < MAX_LINE && (b' '..=b'~').contains(&b) => {
|
|
||||||
self.line[self.line_len] = b;
|
|
||||||
self.line_len += 1;
|
|
||||||
self.grid.put(if secret { b'*' } else { b });
|
|
||||||
}
|
|
||||||
_ => continue,
|
|
||||||
}
|
|
||||||
self.redraw();
|
self.redraw();
|
||||||
self.console.flush();
|
self.console.flush();
|
||||||
}
|
}
|
||||||
self.redraw();
|
done
|
||||||
self.console.flush();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The next chunk of the typed line, reading one first if none is left.
|
/// The oldest waiting caller gets the finished line; once it is all
|
||||||
/// A secret line never reaches the screen, and is forgotten once read.
|
/// read, the next waiting caller's line starts.
|
||||||
fn read(&mut self, secret: bool) -> [usize; 4] {
|
fn serve_waiting(&mut self) {
|
||||||
if self.line_read == self.line_len {
|
loop {
|
||||||
self.read_line(secret);
|
if self.line.unread() {
|
||||||
|
let Some((reply, _)) = self.waiting[0] else { return };
|
||||||
|
self.waiting.rotate_left(1);
|
||||||
|
self.waiting[READERS - 1] = None;
|
||||||
|
let chunk = self.line.chunk();
|
||||||
|
self.console.answer(reply, chunk);
|
||||||
|
// More of it comes when it asks again.
|
||||||
|
if self.line.unread() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let Some((_, secret)) = self.waiting[0] else { return };
|
||||||
|
self.begin_line(secret);
|
||||||
|
if !self.type_keys() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let end = (self.line_read + TEXT_BYTES).min(self.line_len);
|
}
|
||||||
let words = text_words(&self.line[self.line_read..end]);
|
|
||||||
self.line_read = end;
|
/// The next chunk of the typed line now, if there is one; otherwise
|
||||||
if secret && self.line_read == self.line_len {
|
/// the caller is kept and answered once the line is typed.
|
||||||
self.line.fill(0);
|
fn read(&mut self, secret: bool) -> Option<[usize; 4]> {
|
||||||
|
if self.line.unread() {
|
||||||
|
let chunk = self.line.chunk();
|
||||||
|
if !self.line.unread() {
|
||||||
|
self.serve_waiting();
|
||||||
|
}
|
||||||
|
return Some(chunk);
|
||||||
|
}
|
||||||
|
let nobody_waits = self.waiting[0].is_none();
|
||||||
|
if nobody_waits && !self.line.typing() {
|
||||||
|
self.begin_line(secret);
|
||||||
|
}
|
||||||
|
if nobody_waits && self.type_keys() {
|
||||||
|
return Some(self.line.chunk());
|
||||||
|
}
|
||||||
|
let free = self.waiting.iter().position(Option::is_none);
|
||||||
|
match (free, free.and_then(|_| self.console.save_reply())) {
|
||||||
|
(Some(i), Some(reply)) => {
|
||||||
|
self.waiting[i] = Some((reply, secret));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
// Too many waiting: an empty line.
|
||||||
|
_ => Some(text_words(b"\n")),
|
||||||
}
|
}
|
||||||
words
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,8 +256,9 @@ impl<C: Console> Server for TerminalServer<C> {
|
|||||||
|
|
||||||
fn handle_call(&mut self, badge: usize, words: [usize; 4], cap: Option<CapSlot>) -> ([usize; 4], Option<libkos::syscall::Give>) {
|
fn handle_call(&mut self, badge: usize, words: [usize; 4], cap: Option<CapSlot>) -> ([usize; 4], Option<libkos::syscall::Give>) {
|
||||||
let reply = match words[0] {
|
let reply = match words[0] {
|
||||||
types::terminal::READ => self.read(false),
|
// Kept for later: what goes back now is not sent.
|
||||||
types::terminal::READ_SECRET => self.read(true),
|
types::terminal::READ => self.read(false).unwrap_or([0; 4]),
|
||||||
|
types::terminal::READ_SECRET => self.read(true).unwrap_or([0; 4]),
|
||||||
types::terminal::REDRAW => {
|
types::terminal::REDRAW => {
|
||||||
// Taking focus back clears the screen.
|
// Taking focus back clears the screen.
|
||||||
self.console.take_focus();
|
self.console.take_focus();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::server::{BACKSPACE, Console, TerminalServer};
|
|||||||
use server_framework::Server;
|
use server_framework::Server;
|
||||||
use std::vec;
|
use std::vec;
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
use types::bounded::CapSlot;
|
use types::bounded::{CapSlot, SavedReply};
|
||||||
use types::log::{text_of, text_words};
|
use types::log::{text_of, text_words};
|
||||||
use types::terminal::{CTRL_C, KEYS_BIT, TIMER_BIT};
|
use types::terminal::{CTRL_C, KEYS_BIT, TIMER_BIT};
|
||||||
|
|
||||||
@@ -21,6 +21,9 @@ struct Fake {
|
|||||||
rows_drawn: Vec<(usize, usize)>,
|
rows_drawn: Vec<(usize, usize)>,
|
||||||
interrupted: Vec<CapSlot>,
|
interrupted: Vec<CapSlot>,
|
||||||
forgotten: Vec<CapSlot>,
|
forgotten: Vec<CapSlot>,
|
||||||
|
saved: u8,
|
||||||
|
/// Callers answered later, with what.
|
||||||
|
answered: Vec<(SavedReply, [usize; 4])>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Console for Fake {
|
impl Console for Fake {
|
||||||
@@ -37,11 +40,17 @@ impl Console for Fake {
|
|||||||
fn flush_later(&mut self) {
|
fn flush_later(&mut self) {
|
||||||
self.timers += 1;
|
self.timers += 1;
|
||||||
}
|
}
|
||||||
fn next_key(&mut self) -> u8 {
|
/// The keys a test types for a read, then those typed meanwhile.
|
||||||
self.keys.remove(0)
|
|
||||||
}
|
|
||||||
fn poll_key(&mut self) -> Option<u8> {
|
fn poll_key(&mut self) -> Option<u8> {
|
||||||
(!self.typed.is_empty()).then(|| self.typed.remove(0))
|
let from = if self.keys.is_empty() { &mut self.typed } else { &mut self.keys };
|
||||||
|
(!from.is_empty()).then(|| from.remove(0))
|
||||||
|
}
|
||||||
|
fn save_reply(&mut self) -> Option<SavedReply> {
|
||||||
|
self.saved += 1;
|
||||||
|
SavedReply::new(self.saved - 1)
|
||||||
|
}
|
||||||
|
fn answer(&mut self, reply: SavedReply, words: [usize; 4]) {
|
||||||
|
self.answered.push((reply, words));
|
||||||
}
|
}
|
||||||
fn take_focus(&mut self) {
|
fn take_focus(&mut self) {
|
||||||
self.focus_taken += 1;
|
self.focus_taken += 1;
|
||||||
@@ -122,10 +131,12 @@ fn ctrl_c_while_a_program_runs_interrupts_it() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn keys_typed_ahead_start_the_next_line() {
|
fn keys_typed_ahead_start_the_next_line() {
|
||||||
let mut t = term(b"s\n");
|
let mut t = term(&[]);
|
||||||
|
// Typed while nobody reads, then the rest during the read.
|
||||||
t.console_mut_for_test().typed = b"l".to_vec();
|
t.console_mut_for_test().typed = b"l".to_vec();
|
||||||
t.signal(KEYS_BIT);
|
t.signal(KEYS_BIT);
|
||||||
assert!(t.console().interrupted.is_empty());
|
assert!(t.console().interrupted.is_empty());
|
||||||
|
t.console_mut_for_test().typed = b"s\n".to_vec();
|
||||||
assert_eq!(line_of(&mut t), b"ls\n");
|
assert_eq!(line_of(&mut t), b"ls\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,3 +207,51 @@ fn only_the_control_copy_takes_ctrl_c_and_the_latest_counts() {
|
|||||||
t.signal(KEYS_BIT);
|
t.signal(KEYS_BIT);
|
||||||
assert_eq!(t.console().interrupted, [CapSlot::new(9).unwrap()]);
|
assert_eq!(t.console().interrupted, [CapSlot::new(9).unwrap()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn text(words: [usize; 4]) -> Vec<u8> {
|
||||||
|
let (bytes, len) = text_of(words).unwrap();
|
||||||
|
bytes[..len].to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_read_before_anything_is_typed_is_answered_once_the_line_is() {
|
||||||
|
let mut t = term(&[]);
|
||||||
|
t.handle_call(1, [types::terminal::READ, 0, 0, 0], None);
|
||||||
|
assert_eq!(t.console().saved, 1, "the caller is kept");
|
||||||
|
assert!(t.console().answered.is_empty());
|
||||||
|
// Meanwhile the terminal serves others.
|
||||||
|
t.handle_call(2, text_words(b"out"), None);
|
||||||
|
t.console_mut_for_test().typed = b"hi".to_vec();
|
||||||
|
t.signal(KEYS_BIT);
|
||||||
|
assert!(t.console().answered.is_empty(), "not before Enter");
|
||||||
|
t.console_mut_for_test().typed = vec![b'\n'];
|
||||||
|
t.signal(KEYS_BIT);
|
||||||
|
let (reply, words) = t.console().answered[0];
|
||||||
|
assert_eq!((reply, text(words)), (SavedReply::new(0).unwrap(), b"hi\n".to_vec()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn readers_wait_their_turn_each_for_a_line_of_their_own() {
|
||||||
|
let mut t = term(&[]);
|
||||||
|
t.handle_call(1, [types::terminal::READ, 0, 0, 0], None);
|
||||||
|
t.handle_call(2, [types::terminal::READ_SECRET, 0, 0, 0], None);
|
||||||
|
t.console_mut_for_test().typed = b"one\n".to_vec();
|
||||||
|
t.signal(KEYS_BIT);
|
||||||
|
assert_eq!(t.console().answered.len(), 1);
|
||||||
|
t.console_mut_for_test().typed = b"two\n".to_vec();
|
||||||
|
t.signal(KEYS_BIT);
|
||||||
|
let answered: Vec<(u8, Vec<u8>)> = t.console().answered.iter().map(|(r, w)| (r.value(), text(*w))).collect();
|
||||||
|
assert_eq!(answered, [(0, b"one\n".to_vec()), (1, b"two\n".to_vec())]);
|
||||||
|
assert!(t.grid().row(1).starts_with(b"***"), "the second, secret line was echoed as stars");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ctrl_c_while_a_reader_waits_drops_the_line_and_interrupts_nobody() {
|
||||||
|
let mut t = term(&[]);
|
||||||
|
assert_eq!(on_interrupt(&mut t, 7), 0);
|
||||||
|
t.handle_call(1, [types::terminal::READ, 0, 0, 0], None);
|
||||||
|
t.console_mut_for_test().typed = vec![b'x', CTRL_C];
|
||||||
|
t.signal(KEYS_BIT);
|
||||||
|
assert_eq!(text(t.console().answered[0].1), b"\n");
|
||||||
|
assert!(t.console().interrupted.is_empty());
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ use crate::macros::bounded;
|
|||||||
// Kani runs the real kernel with tiny tables; see kernel/src/proofs.
|
// Kani runs the real kernel with tiny tables; see kernel/src/proofs.
|
||||||
bounded!(CapSlot, u8, 0, if cfg!(kani) { 3 } else { 63 });
|
bounded!(CapSlot, u8, 0, if cfg!(kani) { 3 } else { 63 });
|
||||||
bounded!(Priority, u8, 0, 31);
|
bounded!(Priority, u8, 0, 31);
|
||||||
|
// A caller a server thread saved to answer later; a thread keeps up to 8.
|
||||||
|
bounded!(SavedReply, u8, 0, 7);
|
||||||
// Up to a process's whole address window (32 MiB): one request may be big (a
|
// Up to a process's whole address window (32 MiB): one request may be big (a
|
||||||
// KDF's memory); budgets bound how much anyone gets.
|
// KDF's memory); budgets bound how much anyone gets.
|
||||||
bounded!(PageCount, u16, 1, 8192);
|
bounded!(PageCount, u16, 1, 8192);
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ 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_WATCH_DEATH: usize = 36;
|
||||||
pub const SYS_SET_TIMER: usize = 37;
|
pub const SYS_SET_TIMER: usize = 37;
|
||||||
|
pub const SYS_SAVE_REPLY: usize = 39;
|
||||||
|
pub const SYS_REPLY_SAVED: usize = 40;
|
||||||
|
|
||||||
/// `PagePerms` in a register.
|
/// `PagePerms` in a register.
|
||||||
pub const PERMS_READ_ONLY: usize = 0;
|
pub const PERMS_READ_ONLY: usize = 0;
|
||||||
|
|||||||
24
docs/ipc.md
24
docs/ipc.md
@@ -141,9 +141,11 @@ init's calls each server with `server_framework::PING` every 2 s, which
|
|||||||
the framework's loop answers without the server's code. A ping
|
the framework's loop answers without the server's code. A ping
|
||||||
unanswered for 5 s means the server is stuck in a handler; init kills it,
|
unanswered for 5 s means the server is stuck in a handler; init kills it,
|
||||||
and the death is handled as a crash, so the restarted server answers the
|
and the death is handled as a crash, so the restarted server answers the
|
||||||
still-queued ping. The terminal and the permission manager are not
|
still-queued ping. The permission manager is not pinged: while it asks
|
||||||
pinged: their handlers wait for the user (`READ`, a consent prompt),
|
the user for consent it is itself waiting on the terminal inside a
|
||||||
which a ping cannot tell from a hang.
|
handler, which a ping cannot tell from a hang. (The terminal does not
|
||||||
|
wait inside one: a `READ` before the line is typed is saved, `SaveReply`,
|
||||||
|
and answered when Enter is.)
|
||||||
|
|
||||||
A program's `uses` endpoints arrive as badged copies with `Execute` and
|
A program's `uses` endpoints arrive as badged copies with `Execute` and
|
||||||
`Delegate`: the badge (unique per copy) tells the server which client is
|
`Delegate`: the badge (unique per copy) tells the server which client is
|
||||||
@@ -262,6 +264,13 @@ never reveals the sender's process or thread id.
|
|||||||
- `Reply(msg)`: deliver `msg` to the thread in the caller's reply slot and
|
- `Reply(msg)`: deliver `msg` to the thread in the caller's reply slot and
|
||||||
clear the slot. Error if the slot is empty. Never blocks.
|
clear the slot. Error if the slot is empty. Never blocks.
|
||||||
- `ReplyRecv(ep, msg)`: `Reply` if the slot is full, then `Receive(ep)`.
|
- `ReplyRecv(ep, msg)`: `Reply` if the slot is full, then `Receive(ep)`.
|
||||||
|
- `SaveReply`: move the caller in the reply slot into the thread's table of
|
||||||
|
saved replies (8 places) and return which place, a `SavedReply`; the
|
||||||
|
slot is empty again, so the thread can receive others. `NoReply` if the
|
||||||
|
slot is empty, `NoFreeSlot` if the table is full (the reply stays
|
||||||
|
pending).
|
||||||
|
- `ReplySaved(reply, msg)`: deliver `msg` to the caller saved there, as
|
||||||
|
`Reply` would, and free the place. `NoReply` if nothing is saved there.
|
||||||
- `Signal(ntfn)`: let `b` be the capability's badge, or `1` if unbadged. If
|
- `Signal(ntfn)`: let `b` be the capability's badge, or `1` if unbadged. If
|
||||||
a waiter is blocked, wake the first with `b`. Else if the bound thread is
|
a waiter is blocked, wake the first with `b`. Else if the bound thread is
|
||||||
blocked in `Receive`, wake it with `b`. Else `bits |= b`.
|
blocked in `Receive`, wake it with `b`. Else `bits |= b`.
|
||||||
@@ -285,9 +294,12 @@ set it stays masked.
|
|||||||
`NoReply` instead of blocking forever.
|
`NoReply` instead of blocking forever.
|
||||||
- A reply slot names the caller by thread id **and generation**, so a reply
|
- A reply slot names the caller by thread id **and generation**, so a reply
|
||||||
can never reach a different thread that reused the id.
|
can never reach a different thread that reused the id.
|
||||||
- If the server thread dies, its pending caller wakes with `NoReply`.
|
- If the server thread dies, its pending caller and every caller it saved
|
||||||
- If the caller dies, the reply slot is simply stale and `Reply` is a
|
wake with `NoReply`.
|
||||||
successful no-op.
|
- If the caller dies, the reply slot (or saved place) is simply stale and
|
||||||
|
`Reply` (`ReplySaved`) is a successful no-op.
|
||||||
|
- A saved reply is named only within the thread that saved it: it cannot
|
||||||
|
be passed on, and another thread's number means nothing.
|
||||||
|
|
||||||
## Budgets
|
## Budgets
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user