feat(kernel): add Send and Receive IPC syscalls with rendezvous

This commit is contained in:
2026-10-18 00:24:09 +02:00
parent ae6023081a
commit e7835c0435
4 changed files with 326 additions and 0 deletions

View File

@@ -194,6 +194,32 @@ impl<P: Platform> Kernel<P> {
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)]
pub fn create_endpoint_for_test(&mut self) -> types::id::EndpointId {
self.endpoints.create().expect("create endpoint")

View File

@@ -24,6 +24,8 @@ impl<P: Platform> Kernel<P> {
Syscall::Delegate { 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(
domain::error::KernelError::WouldBlock,
)),
@@ -365,4 +367,161 @@ impl<P: Platform> Kernel<P> {
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))
}
}
}

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

View File

@@ -1,5 +1,6 @@
mod capability;
mod exit;
mod ipc;
mod lifecycle;
mod memory;
mod simple;