fix: sched round-robin, cap slot OOB panic, clippy, make ci
- RunList -> intrusive FIFO, no starvation, no silent drop - cap table sized to CapSlot range (kani caught OOB panic) - snake food color test uses const - kani cfg lint, RUST_MIN_STACK for big kernel struct in tests
This commit is contained in:
@@ -196,20 +196,20 @@ impl<P: Platform> Kernel<P> {
|
||||
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_mut(current)
|
||||
&& 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));
|
||||
}
|
||||
if let Ok(t) = self.threads.get(tid)
|
||||
&& matches!(t.state(), ThreadState::Ready)
|
||||
{
|
||||
self.scheduler.next(core);
|
||||
let _ = self.threads.get_mut(tid).map(|t| t.set_running(core));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,26 +265,19 @@ impl<P: Platform> Kernel<P> {
|
||||
|
||||
pub(crate) fn cleanup_threads(&mut self, thread_ids: &[Option<ThreadId>]) {
|
||||
use domain::scheduler::Scheduler;
|
||||
for slot in thread_ids {
|
||||
if let Some(tid) = slot {
|
||||
self.endpoints.remove_thread_from_all(*tid);
|
||||
self.cleanup_wait_any(*tid);
|
||||
self.scheduler.remove(*tid);
|
||||
self.pending_messages[tid.raw() as usize] = None;
|
||||
self.pending_results[tid.raw() as usize] = None;
|
||||
let _ = self.threads.destroy(*tid);
|
||||
}
|
||||
for tid in thread_ids.iter().flatten() {
|
||||
self.endpoints.remove_thread_from_all(*tid);
|
||||
self.cleanup_wait_any(*tid);
|
||||
self.scheduler.remove(*tid);
|
||||
self.pending_messages[tid.raw() as usize] = None;
|
||||
self.pending_results[tid.raw() as usize] = None;
|
||||
let _ = self.threads.destroy(*tid);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn cleanup_wait_any(&mut self, tid: ThreadId) {
|
||||
if let Some(wait_set) = self.wait_sets[tid.raw() as usize].take() {
|
||||
for ep_slot in wait_set.endpoints() {
|
||||
if let Some(_slot) = ep_slot {
|
||||
self.endpoints.remove_thread_from_all(tid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if self.wait_sets[tid.raw() as usize].take().is_some() {
|
||||
self.endpoints.remove_thread_from_all(tid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,12 +305,12 @@ impl<P: Platform> Kernel<P> {
|
||||
.endpoints
|
||||
.get(*id)
|
||||
.map(|ep| *ep.generation())
|
||||
.unwrap_or_else(|| Generation::initial()),
|
||||
.unwrap_or_else(Generation::initial),
|
||||
Resource::Memory(id) => self
|
||||
.regions
|
||||
.get(*id)
|
||||
.map(|r| *r.generation())
|
||||
.unwrap_or_else(|| Generation::initial()),
|
||||
.unwrap_or_else(Generation::initial),
|
||||
_ => Generation::initial(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,11 +42,11 @@ impl<P: Platform> Kernel<P> {
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(e));
|
||||
}
|
||||
|
||||
if let domain::resource::Resource::Memory(region_id) = &resource {
|
||||
if let Some(region) = self.regions.get(*region_id) {
|
||||
self.platform
|
||||
.map_shared(target_pid, region.base(), region.size(), restricted);
|
||||
}
|
||||
if let domain::resource::Resource::Memory(region_id) = &resource
|
||||
&& let Some(region) = self.regions.get(*region_id)
|
||||
{
|
||||
self.platform
|
||||
.map_shared(target_pid, region.base(), region.size(), restricted);
|
||||
}
|
||||
|
||||
SyscallOutcome::Complete(SyscallResult::Ok)
|
||||
|
||||
@@ -15,34 +15,31 @@ impl<P: Platform> Kernel<P> {
|
||||
};
|
||||
|
||||
// Try to deliver fault to a fault handler endpoint (if configured)
|
||||
if let Ok(process) = self.processes.get(pid) {
|
||||
if let Some(handler_slot) = process.fault_handler() {
|
||||
if let Ok(cap) = process.capabilities().get(handler_slot) {
|
||||
if let domain::resource::Resource::Endpoint(ep_id) = cap.resource() {
|
||||
let msg = domain::message::Message::from_words([
|
||||
pid.raw() as usize,
|
||||
fault.code(),
|
||||
fault.address(),
|
||||
fault_pc,
|
||||
]);
|
||||
if let Ok(process) = self.processes.get(pid)
|
||||
&& let Some(handler_slot) = process.fault_handler()
|
||||
&& let Ok(cap) = process.capabilities().get(handler_slot)
|
||||
&& let domain::resource::Resource::Endpoint(ep_id) = cap.resource()
|
||||
{
|
||||
let msg = domain::message::Message::from_words([
|
||||
pid.raw() as usize,
|
||||
fault.code(),
|
||||
fault.address(),
|
||||
fault_pc,
|
||||
]);
|
||||
|
||||
if let Some(ep) = self.endpoints.get_mut(*ep_id) {
|
||||
if ep.has_waiting_receiver() {
|
||||
if let Some(receiver_tid) = ep.dequeue_receiver() {
|
||||
self.pending_results[receiver_tid.raw() as usize] =
|
||||
Some(crate::SyscallResult::Message(msg));
|
||||
if let Some(ep) = self.endpoints.get_mut(*ep_id)
|
||||
&& ep.has_waiting_receiver()
|
||||
&& let Some(receiver_tid) = ep.dequeue_receiver()
|
||||
{
|
||||
self.pending_results[receiver_tid.raw() as usize] =
|
||||
Some(crate::SyscallResult::Message(msg));
|
||||
|
||||
if let Ok(receiver) = self.threads.get_mut(receiver_tid) {
|
||||
let priority = receiver.priority();
|
||||
let _ = receiver.unblock();
|
||||
let core = self.platform.core_id();
|
||||
use domain::scheduler::Scheduler;
|
||||
self.scheduler.add(receiver_tid, priority, core);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Ok(receiver) = self.threads.get_mut(receiver_tid) {
|
||||
let priority = receiver.priority();
|
||||
let _ = receiver.unblock();
|
||||
let core = self.platform.core_id();
|
||||
use domain::scheduler::Scheduler;
|
||||
self.scheduler.add(receiver_tid, priority, core);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ impl<P: Platform> Kernel<P> {
|
||||
message.is_some(),
|
||||
"sender in wait queue had no pending message"
|
||||
);
|
||||
let message = message.unwrap_or_else(|| domain::message::Message::empty());
|
||||
let message = message.unwrap_or_else(domain::message::Message::empty);
|
||||
|
||||
self.pending_results[sender_tid.raw() as usize] = Some(SyscallResult::Ok);
|
||||
|
||||
@@ -289,54 +289,46 @@ impl<P: Platform> Kernel<P> {
|
||||
));
|
||||
}
|
||||
|
||||
for ep_slot in sources.endpoints() {
|
||||
if let Some(slot) = ep_slot {
|
||||
let ep_id =
|
||||
match self.check_cap(caller_pid, *slot, types::permission::Permission::Read) {
|
||||
Ok(cap) => match cap.resource() {
|
||||
domain::resource::Resource::Endpoint(id) => *id,
|
||||
_ => continue,
|
||||
},
|
||||
Err(_) => continue,
|
||||
};
|
||||
for slot in sources.endpoints().iter().flatten() {
|
||||
let ep_id = match self.check_cap(caller_pid, *slot, types::permission::Permission::Read)
|
||||
{
|
||||
Ok(cap) => match cap.resource() {
|
||||
domain::resource::Resource::Endpoint(id) => *id,
|
||||
_ => continue,
|
||||
},
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
if let Some(ep) = self.endpoints.get_mut(ep_id) {
|
||||
if ep.has_waiting_sender() {
|
||||
return SyscallOutcome::Complete(SyscallResult::WakeReason(
|
||||
domain::syscall::WakeReason::MessageReady(*slot),
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(ep) = self.endpoints.get_mut(ep_id)
|
||||
&& ep.has_waiting_sender()
|
||||
{
|
||||
return SyscallOutcome::Complete(SyscallResult::WakeReason(
|
||||
domain::syscall::WakeReason::MessageReady(*slot),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
for irq_slot in sources.interrupts() {
|
||||
if let Some(slot) = irq_slot {
|
||||
if let Ok(cap) =
|
||||
if let Some(slot) = irq_slot
|
||||
&& let Ok(cap) =
|
||||
self.check_cap(caller_pid, *slot, types::permission::Permission::Read)
|
||||
{
|
||||
if let domain::resource::Resource::Interrupt(irq_id) = cap.resource() {
|
||||
if self.irqs.owner(*irq_id).is_some() {
|
||||
return SyscallOutcome::Complete(SyscallResult::WakeReason(
|
||||
domain::syscall::WakeReason::InterruptFired(*slot),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
&& let domain::resource::Resource::Interrupt(irq_id) = cap.resource()
|
||||
&& self.irqs.owner(*irq_id).is_some()
|
||||
{
|
||||
return SyscallOutcome::Complete(SyscallResult::WakeReason(
|
||||
domain::syscall::WakeReason::InterruptFired(*slot),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
for ep_slot in sources.endpoints() {
|
||||
if let Some(slot) = ep_slot {
|
||||
if let Ok(cap) =
|
||||
if let Some(slot) = ep_slot
|
||||
&& let Ok(cap) =
|
||||
self.check_cap(caller_pid, *slot, types::permission::Permission::Read)
|
||||
{
|
||||
if let domain::resource::Resource::Endpoint(id) = cap.resource() {
|
||||
if let Some(ep) = self.endpoints.get_mut(*id) {
|
||||
let _ = ep.enqueue_receiver(caller_tid);
|
||||
}
|
||||
}
|
||||
}
|
||||
&& let domain::resource::Resource::Endpoint(id) = cap.resource()
|
||||
&& let Some(ep) = self.endpoints.get_mut(*id)
|
||||
{
|
||||
let _ = ep.enqueue_receiver(caller_tid);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use domain::process::MAX_THREADS_PER_PROCESS;
|
||||
use platform::Platform;
|
||||
|
||||
use super::{SyscallOutcome, SyscallResult};
|
||||
|
||||
@@ -197,3 +197,18 @@ fn revoke_syscall_without_revoke_permission_fails() {
|
||||
SyscallOutcome::Complete(SyscallResult::Error(_))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_representable_slot_is_rejected_not_panicking() {
|
||||
let (mut k, _) = kernel_with_process();
|
||||
for raw in 1..=CapSlot::MAX {
|
||||
let outcome = k.handle_syscall(Syscall::Send {
|
||||
endpoint: CapSlot::new(raw).unwrap(),
|
||||
message: domain::message::Message::empty(),
|
||||
});
|
||||
assert!(
|
||||
matches!(outcome, SyscallOutcome::Complete(SyscallResult::Error(_))),
|
||||
"slot {raw} should be an error"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use crate::{SyscallOutcome, SyscallResult};
|
||||
use crate::SyscallOutcome;
|
||||
use domain::syscall::Syscall;
|
||||
|
||||
#[test]
|
||||
@@ -21,5 +21,5 @@ fn exit_thread_last_thread_destroys_process() {
|
||||
let (mut k, pid) = kernel_with_process();
|
||||
k.handle_syscall(Syscall::ExitThread);
|
||||
// Process should be gone since it had only one thread
|
||||
assert!(k.process_exists(pid) == false);
|
||||
assert!(!k.process_exists(pid));
|
||||
}
|
||||
|
||||
@@ -65,20 +65,20 @@ impl Kernel<MockPlatform> {
|
||||
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_mut(current)
|
||||
&& 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));
|
||||
}
|
||||
if let Ok(t) = self.threads.get(tid)
|
||||
&& matches!(t.state(), ThreadState::Ready)
|
||||
{
|
||||
self.scheduler.next(core);
|
||||
let _ = self.threads.get_mut(tid).map(|t| t.set_running(core));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ fn send_without_receiver_blocks_caller() {
|
||||
|
||||
#[test]
|
||||
fn receive_without_sender_blocks_caller() {
|
||||
let (mut k, _, pid_b, _, cap_b, tid_b) = setup_ipc_kernel();
|
||||
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);
|
||||
@@ -83,7 +83,7 @@ fn receive_without_sender_blocks_caller() {
|
||||
|
||||
#[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 (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)
|
||||
@@ -108,8 +108,8 @@ fn send_with_waiting_receiver_delivers_and_switches() {
|
||||
|
||||
#[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);
|
||||
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]);
|
||||
@@ -184,7 +184,7 @@ fn ack_interrupt_returns_ok() {
|
||||
|
||||
#[test]
|
||||
fn wait_any_with_ready_source_returns_immediately() {
|
||||
let (mut k, pid_a, _, cap_a, cap_b, tid_b) = setup_ipc_kernel();
|
||||
let (mut k, _pid_a, _, cap_a, cap_b, tid_b) = setup_ipc_kernel();
|
||||
let tid_a = ThreadId::from_raw(0);
|
||||
|
||||
// Thread B sends a message (blocks since no receiver)
|
||||
@@ -206,7 +206,7 @@ fn wait_any_with_ready_source_returns_immediately() {
|
||||
|
||||
#[test]
|
||||
fn wait_any_with_no_ready_source_blocks() {
|
||||
let (mut k, pid_a, _, cap_a, _, _) = setup_ipc_kernel();
|
||||
let (mut k, _pid_a, _, cap_a, _, _) = setup_ipc_kernel();
|
||||
let wait_set = WaitSet::new([Some(cap_a), None, None, None], [None, None, None, None]);
|
||||
let outcome = k.handle_syscall(Syscall::WaitAny { sources: wait_set });
|
||||
assert!(matches!(
|
||||
|
||||
@@ -41,7 +41,7 @@ impl MockPlatform {
|
||||
impl Platform for MockPlatform {
|
||||
fn configure_protection(&mut self, _process: ProcessId, _regions: &[MemoryRegion]) {}
|
||||
fn enable_user_mode(&mut self, _entry: EntryPoint, _stack: StackPointer) -> ! {
|
||||
loop {}
|
||||
panic!("enable_user_mode is not supported in tests")
|
||||
}
|
||||
fn set_timer(&mut self, _duration: TimerDuration) {}
|
||||
fn current_ticks(&self) -> Ticks {
|
||||
@@ -65,16 +65,17 @@ impl Platform for MockPlatform {
|
||||
) {
|
||||
}
|
||||
fn halt(&self) -> ! {
|
||||
loop {}
|
||||
panic!("halt")
|
||||
}
|
||||
fn debug_putchar(&self, _byte: u8) {}
|
||||
|
||||
fn translate_va(&self, process: ProcessId, va: VirtualAddress) -> Option<PhysicalAddress> {
|
||||
for m in &self.mappings {
|
||||
if let Some(m) = m {
|
||||
if m.process.raw() == process.raw() && m.va == va.raw() {
|
||||
return Some(PhysicalAddress::from_raw(m.pa));
|
||||
}
|
||||
if let Some(m) = m
|
||||
&& m.process.raw() == process.raw()
|
||||
&& m.va == va.raw()
|
||||
{
|
||||
return Some(PhysicalAddress::from_raw(m.pa));
|
||||
}
|
||||
}
|
||||
None
|
||||
|
||||
@@ -12,16 +12,16 @@ fn tick_continues_when_time_remaining() {
|
||||
|
||||
#[test]
|
||||
fn tick_preempts_when_time_expired_and_other_thread_ready() {
|
||||
let mut k = test_kernel();
|
||||
let _k = test_kernel();
|
||||
let mock = MockPlatform::new(1);
|
||||
let pool = PagePool::new(PhysicalAddress::from_raw(0x8000_0000), 1024);
|
||||
let mut k = Kernel::new(mock, pool);
|
||||
|
||||
// Boot with quantum=2 so it expires after 2 ticks
|
||||
let (pid, tid1) = k.boot(Priority::DEFAULT, TimeSlice::new(2)).unwrap();
|
||||
let (pid, _tid1) = k.boot(Priority::DEFAULT, TimeSlice::new(2)).unwrap();
|
||||
|
||||
// Spawn a second thread so scheduler has someone to switch to
|
||||
let tid2 = k.spawn_thread_for_test(pid, Priority::DEFAULT, TimeSlice::DEFAULT);
|
||||
let _tid2 = k.spawn_thread_for_test(pid, Priority::DEFAULT, TimeSlice::DEFAULT);
|
||||
|
||||
// First tick: time remaining
|
||||
let d1 = k.handle_tick(CoreId::from_raw(0));
|
||||
|
||||
Reference in New Issue
Block a user