feat(kernel): add Send and Receive IPC syscalls with rendezvous
This commit is contained in:
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 exit;
|
||||
mod ipc;
|
||||
mod lifecycle;
|
||||
mod memory;
|
||||
mod simple;
|
||||
|
||||
Reference in New Issue
Block a user