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);
|
||||
}
|
||||
|
||||
#[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")
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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