feat(kernel): shared ABI decoder+proofs, no-panic gate, syscall storm, kani on shrunk kernel
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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)?))
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user