feat(kernel): add Send and Receive IPC syscalls with rendezvous
This commit is contained in:
@@ -194,6 +194,32 @@ impl<P: Platform> Kernel<P> {
|
|||||||
self.scheduler.remove(tid);
|
self.scheduler.remove(tid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn switch_to_thread_for_test(&mut self, tid: ThreadId) {
|
||||||
|
use domain::scheduler::Scheduler;
|
||||||
|
let core = self.platform.core_id();
|
||||||
|
|
||||||
|
if let Some(current) = self.scheduler.current(core) {
|
||||||
|
if current == tid {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Ok(t) = self.threads.get_mut(current) {
|
||||||
|
if matches!(t.state(), ThreadState::Running(_)) {
|
||||||
|
let priority = t.priority();
|
||||||
|
let _ = t.preempt();
|
||||||
|
self.scheduler.add(current, priority, core);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(t) = self.threads.get(tid) {
|
||||||
|
if matches!(t.state(), ThreadState::Ready) {
|
||||||
|
self.scheduler.next(core);
|
||||||
|
let _ = self.threads.get_mut(tid).map(|t| t.set_running(core));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn create_endpoint_for_test(&mut self) -> types::id::EndpointId {
|
pub fn create_endpoint_for_test(&mut self) -> types::id::EndpointId {
|
||||||
self.endpoints.create().expect("create endpoint")
|
self.endpoints.create().expect("create endpoint")
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ impl<P: Platform> Kernel<P> {
|
|||||||
Syscall::Delegate { slot, to_endpoint, permissions } => {
|
Syscall::Delegate { slot, to_endpoint, permissions } => {
|
||||||
self.handle_delegate(pid, slot, to_endpoint, permissions)
|
self.handle_delegate(pid, slot, to_endpoint, permissions)
|
||||||
}
|
}
|
||||||
|
Syscall::Send { endpoint, message } => self.handle_send(tid, pid, endpoint, message),
|
||||||
|
Syscall::Receive { endpoint } => self.handle_receive(tid, pid, endpoint),
|
||||||
_ => SyscallOutcome::Complete(SyscallResult::Error(
|
_ => SyscallOutcome::Complete(SyscallResult::Error(
|
||||||
domain::error::KernelError::WouldBlock,
|
domain::error::KernelError::WouldBlock,
|
||||||
)),
|
)),
|
||||||
@@ -365,4 +367,161 @@ impl<P: Platform> Kernel<P> {
|
|||||||
Err(e) => SyscallOutcome::Complete(SyscallResult::Error(e)),
|
Err(e) => SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_send(
|
||||||
|
&mut self,
|
||||||
|
caller_tid: types::id::ThreadId,
|
||||||
|
caller_pid: types::id::ProcessId,
|
||||||
|
endpoint_slot: types::bounded::CapSlot,
|
||||||
|
message: domain::message::Message,
|
||||||
|
) -> SyscallOutcome {
|
||||||
|
use domain::scheduler::Scheduler;
|
||||||
|
|
||||||
|
let ep_id = match self.check_cap(
|
||||||
|
caller_pid,
|
||||||
|
endpoint_slot,
|
||||||
|
types::permission::Permission::Execute,
|
||||||
|
) {
|
||||||
|
Ok(cap) => match cap.resource() {
|
||||||
|
domain::resource::Resource::Endpoint(id) => *id,
|
||||||
|
_ => return SyscallOutcome::Complete(SyscallResult::Error(
|
||||||
|
domain::error::KernelError::InvalidEndpoint(types::id::EndpointId::from_raw(0)),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let endpoint = match self.endpoints.get_mut(ep_id) {
|
||||||
|
Some(ep) => ep,
|
||||||
|
None => return SyscallOutcome::Complete(SyscallResult::Error(
|
||||||
|
domain::error::KernelError::InvalidEndpoint(ep_id),
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
|
||||||
|
if endpoint.has_waiting_receiver() {
|
||||||
|
let receiver_tid = match endpoint.dequeue_receiver() {
|
||||||
|
Some(t) => t,
|
||||||
|
None => return SyscallOutcome::Complete(SyscallResult::Error(
|
||||||
|
domain::error::KernelError::WouldBlock,
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.pending_results[receiver_tid.raw() as usize] =
|
||||||
|
Some(SyscallResult::Message(message));
|
||||||
|
|
||||||
|
let receiver = match self.threads.get_mut(receiver_tid) {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||||
|
};
|
||||||
|
let priority = receiver.priority();
|
||||||
|
let _ = receiver.unblock();
|
||||||
|
let core = self.platform.core_id();
|
||||||
|
self.scheduler.add(receiver_tid, priority, core);
|
||||||
|
|
||||||
|
let caller = match self.threads.get_mut(caller_tid) {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||||
|
};
|
||||||
|
let caller_priority = caller.priority();
|
||||||
|
let _ = caller.preempt();
|
||||||
|
self.scheduler.add(caller_tid, caller_priority, core);
|
||||||
|
|
||||||
|
self.scheduler.next(core);
|
||||||
|
let _ = self.threads.get_mut(receiver_tid).map(|t| t.set_running(core));
|
||||||
|
|
||||||
|
SyscallOutcome::SwitchTo(receiver_tid)
|
||||||
|
} else {
|
||||||
|
self.pending_messages[caller_tid.raw() as usize] = Some(message);
|
||||||
|
|
||||||
|
let endpoint = match self.endpoints.get_mut(ep_id) {
|
||||||
|
Some(ep) => ep,
|
||||||
|
None => return SyscallOutcome::Complete(SyscallResult::Error(
|
||||||
|
domain::error::KernelError::InvalidEndpoint(ep_id),
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
let _ = endpoint.enqueue_sender(caller_tid);
|
||||||
|
|
||||||
|
let caller = match self.threads.get_mut(caller_tid) {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||||
|
};
|
||||||
|
let _ = caller.block(domain::thread::BlockReason::Sending(endpoint_slot));
|
||||||
|
self.scheduler.remove(caller_tid);
|
||||||
|
|
||||||
|
SyscallOutcome::BlockCaller(domain::thread::BlockReason::Sending(endpoint_slot))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_receive(
|
||||||
|
&mut self,
|
||||||
|
caller_tid: types::id::ThreadId,
|
||||||
|
caller_pid: types::id::ProcessId,
|
||||||
|
endpoint_slot: types::bounded::CapSlot,
|
||||||
|
) -> SyscallOutcome {
|
||||||
|
use domain::scheduler::Scheduler;
|
||||||
|
|
||||||
|
let ep_id = match self.check_cap(
|
||||||
|
caller_pid,
|
||||||
|
endpoint_slot,
|
||||||
|
types::permission::Permission::Read,
|
||||||
|
) {
|
||||||
|
Ok(cap) => match cap.resource() {
|
||||||
|
domain::resource::Resource::Endpoint(id) => *id,
|
||||||
|
_ => return SyscallOutcome::Complete(SyscallResult::Error(
|
||||||
|
domain::error::KernelError::InvalidEndpoint(types::id::EndpointId::from_raw(0)),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let endpoint = match self.endpoints.get_mut(ep_id) {
|
||||||
|
Some(ep) => ep,
|
||||||
|
None => return SyscallOutcome::Complete(SyscallResult::Error(
|
||||||
|
domain::error::KernelError::InvalidEndpoint(ep_id),
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
|
||||||
|
if endpoint.has_waiting_sender() {
|
||||||
|
let sender_tid = match endpoint.dequeue_sender() {
|
||||||
|
Some(t) => t,
|
||||||
|
None => return SyscallOutcome::Complete(SyscallResult::Error(
|
||||||
|
domain::error::KernelError::WouldBlock,
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let message = self.pending_messages[sender_tid.raw() as usize]
|
||||||
|
.take()
|
||||||
|
.unwrap_or_else(|| domain::message::Message::empty());
|
||||||
|
|
||||||
|
self.pending_results[sender_tid.raw() as usize] = Some(SyscallResult::Ok);
|
||||||
|
|
||||||
|
let sender = match self.threads.get_mut(sender_tid) {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||||
|
};
|
||||||
|
let priority = sender.priority();
|
||||||
|
let _ = sender.unblock();
|
||||||
|
let core = self.platform.core_id();
|
||||||
|
self.scheduler.add(sender_tid, priority, core);
|
||||||
|
|
||||||
|
SyscallOutcome::Complete(SyscallResult::Message(message))
|
||||||
|
} else {
|
||||||
|
let endpoint = match self.endpoints.get_mut(ep_id) {
|
||||||
|
Some(ep) => ep,
|
||||||
|
None => return SyscallOutcome::Complete(SyscallResult::Error(
|
||||||
|
domain::error::KernelError::InvalidEndpoint(ep_id),
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
let _ = endpoint.enqueue_receiver(caller_tid);
|
||||||
|
|
||||||
|
let caller = match self.threads.get_mut(caller_tid) {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||||
|
};
|
||||||
|
let _ = caller.block(domain::thread::BlockReason::Receiving(endpoint_slot));
|
||||||
|
self.scheduler.remove(caller_tid);
|
||||||
|
|
||||||
|
SyscallOutcome::BlockCaller(domain::thread::BlockReason::Receiving(endpoint_slot))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
140
crates/kernel/src/tests/ipc.rs
Normal file
140
crates/kernel/src/tests/ipc.rs
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::{SyscallOutcome, SyscallResult};
|
||||||
|
use domain::message::Message;
|
||||||
|
use domain::syscall::Syscall;
|
||||||
|
use domain::thread::BlockReason;
|
||||||
|
use types::bounded::CapSlot;
|
||||||
|
use types::id::ThreadId;
|
||||||
|
use types::permission::{Permission, PermissionSet};
|
||||||
|
|
||||||
|
fn setup_ipc_kernel() -> (
|
||||||
|
crate::Kernel<MockPlatform>,
|
||||||
|
ProcessId, // process A
|
||||||
|
ProcessId, // process B
|
||||||
|
CapSlot, // endpoint cap for A (Execute perm)
|
||||||
|
CapSlot, // endpoint cap for B (Read perm)
|
||||||
|
ThreadId, // thread B id
|
||||||
|
) {
|
||||||
|
let (mut k, pid_a) = kernel_with_process();
|
||||||
|
|
||||||
|
// Create process B
|
||||||
|
let proc_b_slot = match k.handle_syscall(Syscall::CreateProcess {
|
||||||
|
create_cap: CapSlot::new(0).unwrap(),
|
||||||
|
image: types::address::DataRef::empty(),
|
||||||
|
}) {
|
||||||
|
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
||||||
|
_ => panic!("expected slot"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get process B's pid from the cap
|
||||||
|
let pid_b = ProcessId::from_raw(1);
|
||||||
|
let tid_b = ThreadId::from_raw(1);
|
||||||
|
|
||||||
|
// Create an endpoint
|
||||||
|
let ep_id = k.create_endpoint_for_test();
|
||||||
|
|
||||||
|
// Grant endpoint caps to both processes
|
||||||
|
let cap_a = k.grant_cap_for_test(
|
||||||
|
pid_a,
|
||||||
|
domain::resource::Resource::Endpoint(ep_id),
|
||||||
|
PermissionSet::empty()
|
||||||
|
.with(Permission::Execute)
|
||||||
|
.with(Permission::Read),
|
||||||
|
);
|
||||||
|
|
||||||
|
let cap_b = k.grant_cap_for_test(
|
||||||
|
pid_b,
|
||||||
|
domain::resource::Resource::Endpoint(ep_id),
|
||||||
|
PermissionSet::empty()
|
||||||
|
.with(Permission::Execute)
|
||||||
|
.with(Permission::Read),
|
||||||
|
);
|
||||||
|
|
||||||
|
(k, pid_a, pid_b, cap_a, cap_b, tid_b)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn send_without_receiver_blocks_caller() {
|
||||||
|
let (mut k, _, _, cap_a, _, _) = setup_ipc_kernel();
|
||||||
|
|
||||||
|
let msg = Message::from_words([42, 0, 0, 0]);
|
||||||
|
let outcome = k.handle_syscall(Syscall::Send {
|
||||||
|
endpoint: cap_a,
|
||||||
|
message: msg,
|
||||||
|
});
|
||||||
|
assert!(matches!(outcome, SyscallOutcome::BlockCaller(BlockReason::Sending(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn receive_without_sender_blocks_caller() {
|
||||||
|
let (mut k, _, pid_b, _, cap_b, tid_b) = setup_ipc_kernel();
|
||||||
|
|
||||||
|
// Switch to thread B to call Receive
|
||||||
|
k.switch_to_thread_for_test(tid_b);
|
||||||
|
|
||||||
|
let outcome = k.handle_syscall(Syscall::Receive {
|
||||||
|
endpoint: cap_b,
|
||||||
|
});
|
||||||
|
assert!(matches!(outcome, SyscallOutcome::BlockCaller(BlockReason::Receiving(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn send_with_waiting_receiver_delivers_and_switches() {
|
||||||
|
let (mut k, pid_a, pid_b, cap_a, cap_b, tid_b) = setup_ipc_kernel();
|
||||||
|
let tid_a = ThreadId::from_raw(0);
|
||||||
|
|
||||||
|
// Thread B calls Receive first (blocks)
|
||||||
|
k.switch_to_thread_for_test(tid_b);
|
||||||
|
k.handle_syscall(Syscall::Receive { endpoint: cap_b });
|
||||||
|
|
||||||
|
// Thread A sends
|
||||||
|
k.switch_to_thread_for_test(tid_a);
|
||||||
|
let msg = Message::from_words([42, 0, 0, 0]);
|
||||||
|
let outcome = k.handle_syscall(Syscall::Send {
|
||||||
|
endpoint: cap_a,
|
||||||
|
message: msg,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should switch to the receiver
|
||||||
|
assert!(matches!(outcome, SyscallOutcome::SwitchTo(t) if t == tid_b));
|
||||||
|
|
||||||
|
// Receiver should have pending message
|
||||||
|
let result = k.pending_result(tid_b);
|
||||||
|
assert!(result.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn receive_with_waiting_sender_delivers_immediately() {
|
||||||
|
let (mut k, pid_a, pid_b, cap_a, cap_b, tid_b) = setup_ipc_kernel();
|
||||||
|
let tid_a = ThreadId::from_raw(0);
|
||||||
|
|
||||||
|
// Thread A sends first (blocks)
|
||||||
|
let msg = Message::from_words([99, 0, 0, 0]);
|
||||||
|
k.handle_syscall(Syscall::Send {
|
||||||
|
endpoint: cap_a,
|
||||||
|
message: msg,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Thread B calls Receive (should get message immediately)
|
||||||
|
k.switch_to_thread_for_test(tid_b);
|
||||||
|
let outcome = k.handle_syscall(Syscall::Receive { endpoint: cap_b });
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
outcome,
|
||||||
|
SyscallOutcome::Complete(SyscallResult::Message(m)) if m.word(0) == 99
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn send_checks_endpoint_capability() {
|
||||||
|
let (mut k, _, _, _, _, _) = setup_ipc_kernel();
|
||||||
|
// slot 10 doesn't exist
|
||||||
|
let outcome = k.handle_syscall(Syscall::Send {
|
||||||
|
endpoint: CapSlot::new(10).unwrap(),
|
||||||
|
message: Message::empty(),
|
||||||
|
});
|
||||||
|
assert!(matches!(
|
||||||
|
outcome,
|
||||||
|
SyscallOutcome::Complete(SyscallResult::Error(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
mod capability;
|
mod capability;
|
||||||
mod exit;
|
mod exit;
|
||||||
|
mod ipc;
|
||||||
mod lifecycle;
|
mod lifecycle;
|
||||||
mod memory;
|
mod memory;
|
||||||
mod simple;
|
mod simple;
|
||||||
|
|||||||
Reference in New Issue
Block a user