feat(kernel): shared ABI decoder+proofs, no-panic gate, syscall storm, kani on shrunk kernel

This commit is contained in:
2026-10-26 23:44:09 +01:00
parent 154599e3af
commit 130a82f50d
41 changed files with 1583 additions and 404 deletions

View File

@@ -8,21 +8,26 @@
/// always exists before it does.
pub fn descendants<const N: usize>(parents: &[Option<u16>; N], root: usize) -> [bool; N] {
let mut marked = [false; N];
if root >= N {
let Some(r) = marked.get_mut(root) else {
return marked;
}
marked[root] = true;
};
*r = true;
// Each pass extends the marked set by at least one generation of
// children; a chain in an N-node forest is at most N - 1 long.
for _ in 0..N {
let mut changed = false;
for i in 0..N {
if !marked[i]
&& let Some(p) = parents[i].map(usize::from)
&& p < N
&& marked[p]
let parent_marked = parents
.get(i)
.copied()
.flatten()
.and_then(|p| marked.get(usize::from(p)).copied())
.unwrap_or(false);
if let Some(m) = marked.get_mut(i)
&& !*m
&& parent_marked
{
marked[i] = true;
*m = true;
changed = true;
}
}

View File

@@ -101,15 +101,20 @@ impl CapabilityTable {
}
}
fn slot(&self, slot: CapSlot) -> Option<&Capability> {
self.slots.get(slot.value() as usize)?.as_ref()
}
/// Generation of an occupied slot.
pub fn slot_generation(&self, slot: CapSlot) -> Option<Generation> {
self.slots[slot.value() as usize].as_ref()?;
self.slot(slot)?;
self.generations.current(slot.value() as usize)
}
pub fn get_mut(&mut self, slot: CapSlot) -> Result<&mut Capability, KernelError> {
self.slots[slot.value() as usize]
.as_mut()
self.slots
.get_mut(slot.value() as usize)
.and_then(Option::as_mut)
.ok_or(KernelError::InvalidSlot(slot))
}
@@ -121,27 +126,28 @@ impl CapabilityTable {
}
pub fn get(&self, slot: CapSlot) -> Result<&Capability, KernelError> {
let idx = slot.value() as usize;
self.slots[idx]
.as_ref()
.ok_or(KernelError::InvalidSlot(slot))
self.slot(slot).ok_or(KernelError::InvalidSlot(slot))
}
pub fn insert(&mut self, cap: Capability) -> Result<CapSlot, KernelError> {
let idx = (0..MAX_CAPS_PER_PROCESS)
.find(|&i| self.slots[i].is_none() && self.generations.is_usable(i))
let generations = &self.generations;
let (idx, free) = self
.slots
.iter_mut()
.enumerate()
.find(|(i, s)| s.is_none() && generations.is_usable(*i))
.ok_or(KernelError::NoFreeSlot)?;
self.slots[idx] = Some(cap);
CapSlot::new(idx as u8).ok_or(KernelError::NoFreeSlot)
let slot = CapSlot::new(idx as u8).ok_or(KernelError::NoFreeSlot)?;
*free = Some(cap);
Ok(slot)
}
pub fn remove(&mut self, slot: CapSlot) -> Result<(), KernelError> {
let idx = slot.value() as usize;
if self.slots[idx].is_none() {
let taken = self.slots.get_mut(idx).and_then(Option::take);
if taken.is_none() {
return Err(KernelError::InvalidSlot(slot));
}
self.slots[idx] = None;
self.generations.advance(idx);
Ok(())
}

View File

@@ -57,6 +57,14 @@ impl Endpoint {
self.waiting_receivers.pop()
}
pub fn senders(&self) -> impl Iterator<Item = ThreadId> + '_ {
self.waiting_senders.iter()
}
pub fn receivers(&self) -> impl Iterator<Item = ThreadId> + '_ {
self.waiting_receivers.iter()
}
pub fn is_sender(&self, thread: ThreadId) -> bool {
self.waiting_senders.contains(thread)
}
@@ -82,7 +90,7 @@ impl Endpoint {
}
}
pub const MAX_ENDPOINTS: usize = 256;
pub const MAX_ENDPOINTS: usize = if cfg!(kani) { 2 } else { 256 };
pub struct EndpointTable {
endpoints: [Option<Endpoint>; MAX_ENDPOINTS],
@@ -98,10 +106,14 @@ impl EndpointTable {
}
pub fn create(&mut self, owner: ProcessId) -> Option<EndpointId> {
let idx = (0..MAX_ENDPOINTS)
.find(|&i| self.endpoints[i].is_none() && self.generations.is_usable(i))?;
let generations = &self.generations;
let (idx, slot) = self
.endpoints
.iter_mut()
.enumerate()
.find(|(i, s)| s.is_none() && generations.is_usable(*i))?;
let id = EndpointId::from_raw(idx as u16);
self.endpoints[idx] = Some(Endpoint::new(id, owner));
*slot = Some(Endpoint::new(id, owner));
Some(id)
}
@@ -111,6 +123,10 @@ impl EndpointTable {
self.generations.current(id.raw() as usize)
}
pub fn iter(&self) -> impl Iterator<Item = &Endpoint> {
self.endpoints.iter().flatten()
}
pub fn ids_owned_by(&self, owner: ProcessId) -> impl Iterator<Item = EndpointId> + '_ {
self.endpoints
.iter()

View File

@@ -1,6 +1,6 @@
use types::id::ThreadId;
pub const MAX_WAITERS: usize = 16;
pub const MAX_WAITERS: usize = if cfg!(kani) { 2 } else { 16 };
/// Bounded FIFO of blocked threads. Order is preserved across pops and
/// removals, so no waiter can be overtaken.
@@ -21,13 +21,13 @@ impl<const N: usize> WaitQueue<N> {
if self.len == N || self.contains(thread) {
return None;
}
self.entries[self.len] = Some(thread);
*self.entries.get_mut(self.len)? = Some(thread);
self.len += 1;
Some(())
}
pub fn pop(&mut self) -> Option<ThreadId> {
let head = self.entries[0]?;
let head = self.entries.first().copied().flatten()?;
self.close_gap(0);
Some(head)
}
@@ -45,20 +45,33 @@ impl<const N: usize> WaitQueue<N> {
}
}
pub fn iter(&self) -> impl Iterator<Item = ThreadId> + '_ {
self.entries.iter().take(self.len).flatten().copied()
}
pub fn contains(&self, thread: ThreadId) -> bool {
self.entries[..self.len].contains(&Some(thread))
self.entries
.iter()
.take(self.len)
.any(|e| *e == Some(thread))
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
/// Removes entry `i`, shifting later entries forward to keep order.
fn close_gap(&mut self, i: usize) {
for j in i..self.len - 1 {
self.entries[j] = self.entries[j + 1];
for j in i..self.len.saturating_sub(1) {
let next = self.entries.get(j + 1).copied().flatten();
if let Some(e) = self.entries.get_mut(j) {
*e = next;
}
}
self.len = self.len.saturating_sub(1);
if let Some(last) = self.entries.get_mut(self.len) {
*last = None;
}
self.len -= 1;
self.entries[self.len] = None;
}
}

View File

@@ -8,7 +8,7 @@ use types::{
#[path = "tests.rs"]
mod tests;
const MAX_IRQS: usize = 256;
const MAX_IRQS: usize = if cfg!(kani) { 2 } else { 256 };
/// Where an interrupt is delivered: a signal on a notification, through a
/// capability snapshot (generation and badge) taken when the route was set.

View File

@@ -1,4 +1,18 @@
#![no_std]
// A kernel panic takes the whole system down, so kernel code may not
// contain any way to panic. Tests and proofs are exempt.
#![cfg_attr(
not(any(test, kani)),
deny(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::unreachable,
clippy::todo,
clippy::unimplemented,
clippy::indexing_slicing
)
)]
#[cfg(kani)]
pub mod proofs;

View File

@@ -18,7 +18,7 @@ mod proofs;
pub const PAGE_SIZE: usize = 4096;
/// Frames the kernel can track. The board's user pool must fit.
pub const MAX_FRAMES: usize = 4096;
pub const MAX_FRAMES: usize = if cfg!(kani) { 6 } else { 4096 };
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrameUse {
@@ -88,13 +88,26 @@ impl<const N: usize> FrameTable<N> {
self.base.offset(idx)
}
/// The frames that exist (`total` is clamped to `N` at construction).
fn live(&self) -> &[Option<FrameUse>] {
self.frames.get(..self.total).unwrap_or(&[])
}
fn live_mut(&mut self) -> &mut [Option<FrameUse>] {
self.frames.get_mut(..self.total).unwrap_or(&mut [])
}
pub fn get(&self, pa: PhysicalAddress) -> Option<&FrameUse> {
self.frames[self.index_of(pa)?].as_ref()
self.frames.get(self.index_of(pa)?)?.as_ref()
}
pub fn alloc(&mut self, usage: FrameUse) -> Option<PhysicalAddress> {
let idx = self.frames[..self.total].iter().position(Option::is_none)?;
self.frames[idx] = Some(usage);
let (idx, slot) = self
.live_mut()
.iter_mut()
.enumerate()
.find(|(_, f)| f.is_none())?;
*slot = Some(usage);
self.free -= 1;
Some(self.address_of(idx))
}
@@ -110,8 +123,8 @@ impl<const N: usize> FrameTable<N> {
}
let mut run = 0;
let mut start = None;
for idx in 0..self.total {
if self.frames[idx].is_some() {
for (idx, f) in self.live().iter().enumerate() {
if f.is_some() {
run = 0;
continue;
}
@@ -122,8 +135,9 @@ impl<const N: usize> FrameTable<N> {
}
}
let start = start?;
for i in 0..count {
self.frames[start + i] = Some(usage(i));
let run = self.live_mut().get_mut(start..start + count)?;
for (i, f) in run.iter_mut().enumerate() {
*f = Some(usage(i));
}
self.free -= count;
Some(self.address_of(start))
@@ -132,7 +146,7 @@ impl<const N: usize> FrameTable<N> {
/// Frees an allocated frame and reports what it was used for.
pub fn free(&mut self, pa: PhysicalAddress) -> Option<FrameUse> {
let idx = self.index_of(pa)?;
let usage = self.frames[idx].take()?;
let usage = self.frames.get_mut(idx)?.take()?;
self.free += 1;
Some(usage)
}
@@ -140,7 +154,11 @@ impl<const N: usize> FrameTable<N> {
/// Changes the use of an allocated frame (e.g. moving it to another
/// process). Fails on free frames.
pub fn retag(&mut self, pa: PhysicalAddress, usage: FrameUse) -> bool {
match self.index_of(pa).and_then(|i| self.frames[i].as_mut()) {
match self
.index_of(pa)
.and_then(|i| self.frames.get_mut(i))
.and_then(Option::as_mut)
{
Some(slot) => {
*slot = usage;
true
@@ -159,22 +177,19 @@ impl<const N: usize> FrameTable<N> {
return None;
}
let page = u32::try_from(va.raw() / PAGE_SIZE).ok()?;
self.frames[..self.total]
.iter()
.enumerate()
.find_map(|(idx, f)| match f {
Some(FrameUse::User {
owner: o,
page: p,
perms,
}) if *o == owner && *p == page => Some((self.address_of(idx), *perms)),
_ => None,
})
self.live().iter().enumerate().find_map(|(idx, f)| match f {
Some(FrameUse::User {
owner: o,
page: p,
perms,
}) if *o == owner && *p == page => Some((self.address_of(idx), *perms)),
_ => None,
})
}
/// Every allocated frame and its use.
pub fn iter(&self) -> impl Iterator<Item = (PhysicalAddress, &FrameUse)> + '_ {
self.frames[..self.total]
self.live()
.iter()
.enumerate()
.filter_map(|(idx, f)| f.as_ref().map(|u| (self.address_of(idx), u)))
@@ -185,7 +200,7 @@ impl<const N: usize> FrameTable<N> {
&self,
owner: ProcessId,
) -> impl Iterator<Item = (PhysicalAddress, VirtualAddress)> + '_ {
self.frames[..self.total]
self.live()
.iter()
.enumerate()
.filter_map(move |(idx, f)| match f {
@@ -201,7 +216,7 @@ impl<const N: usize> FrameTable<N> {
/// how many were freed.
pub fn free_owned_by(&mut self, owner: ProcessId) -> usize {
let mut freed = 0;
for slot in &mut self.frames[..self.total] {
for slot in self.live_mut().iter_mut() {
let owned = matches!(
slot,
Some(FrameUse::User { owner: o, .. } | FrameUse::PageTable { owner: o }) if *o == owner

View File

@@ -18,8 +18,8 @@ mod quota_tests;
#[path = "tests.rs"]
mod tests;
pub const MAX_REGIONS: usize = 64;
pub const MAX_REGION_MAPPINGS: usize = 4;
pub const MAX_REGIONS: usize = if cfg!(kani) { 1 } else { 64 };
pub const MAX_REGION_MAPPINGS: usize = if cfg!(kani) { 2 } else { 4 };
/// Where a region's frames come from.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -136,7 +136,10 @@ impl RegionTable {
}
fn free_slot(&self) -> Option<usize> {
(0..MAX_REGIONS).find(|&i| self.regions[i].is_none() && self.generations.is_usable(i))
self.regions
.iter()
.enumerate()
.position(|(i, r)| r.is_none() && self.generations.is_usable(i))
}
/// The id the next `insert` will return.
@@ -147,7 +150,7 @@ impl RegionTable {
pub fn insert(&mut self, region: Region) -> Option<RegionId> {
let idx = self.free_slot()?;
self.regions[idx] = Some(region);
*self.regions.get_mut(idx)? = Some(region);
Some(RegionId::from_raw(idx as u16))
}

View File

@@ -31,8 +31,9 @@ impl Message {
&self.words
}
/// Word `index`, or 0 past the end.
pub fn word(&self, index: usize) -> usize {
self.words[index]
self.words.get(index).copied().unwrap_or(0)
}
pub fn cap(&self) -> Option<CapSlot> {

View File

@@ -17,7 +17,7 @@ mod tests;
#[path = "proofs.rs"]
mod proofs;
pub const MAX_NOTIFICATIONS: usize = 128;
pub const MAX_NOTIFICATIONS: usize = if cfg!(kani) { 1 } else { 128 };
/// What the kernel must do after a signal.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -84,6 +84,10 @@ impl Notification {
self.bits != 0
}
pub fn waiters(&self) -> impl Iterator<Item = ThreadId> + '_ {
self.waiters.iter()
}
pub fn is_waiter(&self, thread: ThreadId) -> bool {
self.waiters.contains(thread)
}
@@ -132,10 +136,14 @@ impl NotificationTable {
}
pub fn create(&mut self, owner: ProcessId) -> Option<NotificationId> {
let idx = (0..MAX_NOTIFICATIONS)
.find(|&i| self.slots[i].is_none() && self.generations.is_usable(i))?;
let generations = &self.generations;
let (idx, slot) = self
.slots
.iter_mut()
.enumerate()
.find(|(i, s)| s.is_none() && generations.is_usable(*i))?;
let id = NotificationId::from_raw(idx as u16);
self.slots[idx] = Some(Notification::new(id, owner));
*slot = Some(Notification::new(id, owner));
Some(id)
}
@@ -158,6 +166,10 @@ impl NotificationTable {
Some(n)
}
pub fn iter(&self) -> impl Iterator<Item = &Notification> {
self.slots.iter().flatten()
}
pub fn ids_owned_by(&self, owner: ProcessId) -> impl Iterator<Item = NotificationId> + '_ {
self.slots
.iter()

View File

@@ -10,8 +10,8 @@ use crate::{capability::CapabilityTable, error::KernelError, generations::SlotGe
#[path = "tests.rs"]
mod tests;
pub const MAX_THREADS_PER_PROCESS: usize = 4;
pub const MAX_PROCESSES: usize = 64;
pub const MAX_THREADS_PER_PROCESS: usize = if cfg!(kani) { 2 } else { 4 };
pub const MAX_PROCESSES: usize = if cfg!(kani) { 3 } else { 64 };
pub struct Process {
id: ProcessId,
@@ -98,12 +98,16 @@ impl ProcessTable {
capabilities: CapabilityTable,
fault_handler: Option<CapSlot>,
) -> Result<ProcessId, KernelError> {
let idx = (0..MAX_PROCESSES)
.find(|&i| self.processes[i].is_none() && self.generations.is_usable(i))
let generations = &self.generations;
let (idx, slot) = self
.processes
.iter_mut()
.enumerate()
.find(|(i, s)| s.is_none() && generations.is_usable(*i))
.ok_or(KernelError::TooManyProcesses)?;
let id = ProcessId::from_raw(idx as u16);
self.processes[idx] = Some(Process::new(id, capabilities, fault_handler));
*slot = Some(Process::new(id, capabilities, fault_handler));
Ok(id)
}

View File

@@ -1,6 +1,6 @@
use types::id::{EndpointId, IrqId, NotificationId, ProcessId, RegionId};
#[derive(Clone)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Resource {
Memory(RegionId),
Endpoint(EndpointId),

View File

@@ -5,8 +5,8 @@ use types::{
use super::{Scheduler, SchedulerDecision, per_core::PerCoreScheduler};
const MAX_CORES: usize = 8;
const MAX_SCHEDULED_THREADS: usize = 256;
const MAX_CORES: usize = if cfg!(kani) { 1 } else { 8 };
const MAX_SCHEDULED_THREADS: usize = crate::thread::MAX_THREADS;
const BALANCE_THRESHOLD: usize = 2;
struct ThreadPlacement {
@@ -25,32 +25,47 @@ impl MultiCoreScheduler {
let cores = core::array::from_fn(|i| PerCoreScheduler::new(CoreId::from_raw(i as u8)));
Self {
cores,
num_cores,
num_cores: num_cores.min(MAX_CORES as u8),
placements: [const { None }; MAX_SCHEDULED_THREADS],
}
}
fn core(&self, core: CoreId) -> Option<&PerCoreScheduler> {
self.active().get(core.raw() as usize)
}
fn core_mut(&mut self, core: CoreId) -> Option<&mut PerCoreScheduler> {
let n = self.num_cores as usize;
self.cores.get_mut(..n)?.get_mut(core.raw() as usize)
}
fn active(&self) -> &[PerCoreScheduler] {
self.cores.get(..self.num_cores as usize).unwrap_or(&[])
}
fn placement(&self, thread: ThreadId) -> Option<&ThreadPlacement> {
self.placements.get(thread.raw() as usize)?.as_ref()
}
/// Whether `thread` waits in some core's run queue.
pub fn is_queued(&self, thread: ThreadId) -> bool {
self.cores[..self.num_cores as usize]
.iter()
.any(|c| c.is_queued(thread))
self.active().iter().any(|c| c.is_queued(thread))
}
pub fn current(&self, core: CoreId) -> Option<ThreadId> {
self.cores[core.raw() as usize].current()
self.core(core)?.current()
}
pub fn set_current(&mut self, core: CoreId, thread: ThreadId) {
self.cores[core.raw() as usize].set_current(thread);
if let Some(c) = self.core_mut(core) {
c.set_current(thread);
}
}
/// Runs `thread` next on `core` regardless of its place in the queue.
/// Fails if it is not queued there.
pub fn run_now(&mut self, core: CoreId, thread: ThreadId) -> bool {
self.cores
.get_mut(core.raw() as usize)
.is_some_and(|c| c.run_now(thread))
self.core_mut(core).is_some_and(|c| c.run_now(thread))
}
fn thread_count_on(&self, core: CoreId) -> usize {
@@ -75,38 +90,49 @@ impl MultiCoreScheduler {
impl Scheduler for MultiCoreScheduler {
fn add(&mut self, thread: ThreadId, priority: Priority, core: CoreId) {
let idx = thread.raw() as usize;
self.placements[idx] = Some(ThreadPlacement { core, priority });
self.cores[core.raw() as usize].add(thread, priority);
if self.core(core).is_none() {
return;
}
let Some(placement) = self.placements.get_mut(thread.raw() as usize) else {
return;
};
*placement = Some(ThreadPlacement { core, priority });
if let Some(c) = self.core_mut(core) {
c.add(thread, priority);
}
}
fn remove(&mut self, thread: ThreadId) {
let idx = thread.raw() as usize;
if let Some(placement) = self.placements[idx].take() {
let core_idx = placement.core.raw() as usize;
self.cores[core_idx].remove(thread);
if self.cores[core_idx].current() == Some(thread) {
self.cores[core_idx].clear_current();
let Some(placement) = self
.placements
.get_mut(thread.raw() as usize)
.and_then(Option::take)
else {
return;
};
if let Some(c) = self.core_mut(placement.core) {
c.remove(thread);
if c.current() == Some(thread) {
c.clear_current();
}
}
}
fn next(&mut self, core: CoreId) -> Option<ThreadId> {
self.cores[core.raw() as usize].pick_next()
self.core_mut(core)?.pick_next()
}
fn tick(&mut self, core: CoreId, current: ThreadId) -> SchedulerDecision {
let idx = current.raw() as usize;
if let Some(placement) = &self.placements[idx] {
let current_priority = placement.priority;
let core_idx = core.raw() as usize;
if let Some(highest) = self.cores[core_idx].highest_waiting_priority()
&& highest > current_priority
&& let Some(next) = self.cores[core_idx].pick_next()
{
self.cores[core_idx].add(current, current_priority);
return SchedulerDecision::Preempt(next);
}
let Some(current_priority) = self.placement(current).map(|p| p.priority) else {
return SchedulerDecision::Continue;
};
if let Some(c) = self.core_mut(core)
&& let Some(highest) = c.highest_waiting_priority()
&& highest > current_priority
&& let Some(next) = c.pick_next()
{
c.add(current, current_priority);
return SchedulerDecision::Preempt(next);
}
SchedulerDecision::Continue
}
@@ -117,13 +143,14 @@ impl Scheduler for MultiCoreScheduler {
_ => return,
};
if self.thread_count_on(busiest) > self.thread_count_on(lightest) + BALANCE_THRESHOLD {
let busiest_idx = busiest.raw() as usize;
if let Some((thread, priority)) = self.cores[busiest_idx].steal_lowest() {
let lightest_idx = lightest.raw() as usize;
self.cores[lightest_idx].add(thread, priority);
let tid = thread.raw() as usize;
self.placements[tid] = Some(ThreadPlacement {
if self.thread_count_on(busiest) > self.thread_count_on(lightest) + BALANCE_THRESHOLD
&& let Some((thread, priority)) = self.core_mut(busiest).and_then(|c| c.steal_lowest())
{
if let Some(c) = self.core_mut(lightest) {
c.add(thread, priority);
}
if let Some(p) = self.placements.get_mut(thread.raw() as usize) {
*p = Some(ThreadPlacement {
core: lightest,
priority,
});

View File

@@ -58,62 +58,85 @@ impl PriorityQueue {
self.links.get(thread.raw() as usize)
}
fn links_mut(&mut self, thread: ThreadId) -> Option<&mut Links> {
self.links.get_mut(thread.raw() as usize)
}
/// Appends `thread` at `priority`. A thread that is already queued is
/// left where it is; returns whether it was added.
pub fn add(&mut self, thread: ThreadId, priority: Priority) -> bool {
let idx = priority.value() as usize;
let t = thread.raw() as usize;
if t >= MAX_THREADS || idx >= NUM_PRIORITIES || self.links[t].queued_at.is_some() {
let queued = self.link(thread).is_none_or(|l| l.queued_at.is_some());
let Some(old_tail) = self.lists.get(idx).map(|l| l.tail) else {
return false;
};
if queued {
return false;
}
let old_tail = self.lists[idx].tail;
self.links[t] = Links {
prev: old_tail,
next: None,
queued_at: Some(idx as u8),
};
match old_tail {
Some(tail) => self.links[tail.raw() as usize].next = Some(thread),
None => self.lists[idx].head = Some(thread),
if let Some(l) = self.links_mut(thread) {
*l = Links {
prev: old_tail,
next: None,
queued_at: Some(idx as u8),
};
}
match old_tail.and_then(|t| self.links_mut(t)) {
Some(tail) => tail.next = Some(thread),
None => {
if let Some(list) = self.lists.get_mut(idx) {
list.head = Some(thread);
}
}
}
if let Some(list) = self.lists.get_mut(idx) {
list.tail = Some(thread);
}
self.lists[idx].tail = Some(thread);
self.bitmap |= 1 << idx;
true
}
/// Unlinks `thread` from whichever level it is queued at.
pub fn remove(&mut self, thread: ThreadId) -> bool {
let t = thread.raw() as usize;
let Some(Links {
prev,
next,
queued_at: Some(level),
}) = self.links.get(t).copied()
}) = self.link(thread).copied()
else {
return false;
};
let idx = level as usize;
match prev {
Some(p) => self.links[p.raw() as usize].next = next,
None => self.lists[idx].head = next,
match prev.and_then(|p| self.links_mut(p)) {
Some(p) => p.next = next,
None => {
if let Some(list) = self.lists.get_mut(idx) {
list.head = next;
}
}
}
match next {
Some(n) => self.links[n.raw() as usize].prev = prev,
None => self.lists[idx].tail = prev,
match next.and_then(|n| self.links_mut(n)) {
Some(n) => n.prev = prev,
None => {
if let Some(list) = self.lists.get_mut(idx) {
list.tail = prev;
}
}
}
self.links[t] = Links {
prev: None,
next: None,
queued_at: None,
};
if self.lists[idx].head.is_none() {
if let Some(l) = self.links_mut(thread) {
*l = Links {
prev: None,
next: None,
queued_at: None,
};
}
if self.lists.get(idx).is_none_or(|l| l.head.is_none()) {
self.bitmap &= !(1 << idx);
}
true
}
fn pop_level(&mut self, idx: usize) -> Option<(ThreadId, Priority)> {
let thread = self.lists[idx].head?;
let thread = self.lists.get(idx)?.head?;
self.remove(thread);
Some((thread, Priority::new(idx as u8)?))
}

View File

@@ -307,3 +307,22 @@ fn run_now_rejects_a_thread_that_is_not_queued() {
assert!(!s.run_now(core(0), thread(9)));
assert_eq!(s.current(core(0)), None);
}
#[test]
fn an_unknown_core_is_ignored_not_a_panic() {
let mut s = MultiCoreScheduler::new(1);
s.add(thread(1), prio(10), core(200));
assert_eq!(s.next(core(200)), None);
assert_eq!(s.current(core(200)), None);
s.set_current(core(200), thread(1));
assert!(!s.run_now(core(200), thread(1)));
}
#[test]
fn an_out_of_range_thread_id_is_ignored_not_a_panic() {
let mut s = MultiCoreScheduler::new(1);
s.add(thread(60000), prio(10), core(0));
s.remove(thread(60000));
assert!(!s.is_queued(thread(60000)));
let _ = s.tick(core(0), thread(60000));
}

View File

@@ -229,7 +229,7 @@ impl Thread {
}
}
pub const MAX_THREADS: usize = 256;
pub const MAX_THREADS: usize = if cfg!(kani) { 3 } else { 256 };
pub struct ThreadTable {
threads: [Option<Thread>; MAX_THREADS],
@@ -252,7 +252,10 @@ impl ThreadTable {
}
fn free_slot(&self) -> Option<usize> {
(0..MAX_THREADS).find(|&i| self.threads[i].is_none() && self.generations.is_usable(i))
self.threads
.iter()
.enumerate()
.position(|(i, t)| t.is_none() && self.generations.is_usable(i))
}
pub fn spawn(
@@ -265,7 +268,10 @@ impl ThreadTable {
let idx = self.free_slot().ok_or(KernelError::TooManyThreads)?;
let id = ThreadId::from_raw(idx as u16);
self.threads[idx] = Some(Thread::new(
*self
.threads
.get_mut(idx)
.ok_or(KernelError::TooManyThreads)? = Some(Thread::new(
id,
process,
priority,
@@ -286,7 +292,10 @@ impl ThreadTable {
let idx = self.free_slot().ok_or(KernelError::TooManyThreads)?;
let id = ThreadId::from_raw(idx as u16);
self.threads[idx] = Some(Thread::with_context(
*self
.threads
.get_mut(idx)
.ok_or(KernelError::TooManyThreads)? = Some(Thread::with_context(
id,
process,
priority,