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

@@ -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;