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:
@@ -1,3 +1,7 @@
|
|||||||
[target.riscv64gc-unknown-none-elf]
|
[target.riscv64gc-unknown-none-elf]
|
||||||
runner = "qemu-system-riscv64 -machine virt -nographic -bios default -kernel"
|
runner = "qemu-system-riscv64 -machine virt -nographic -bios default -kernel"
|
||||||
rustflags = ["-C", "panic=abort"]
|
rustflags = ["-C", "panic=abort"]
|
||||||
|
|
||||||
|
[env]
|
||||||
|
# Kernel tables are large fixed arrays; tests build `Kernel` by value.
|
||||||
|
RUST_MIN_STACK = "33554432"
|
||||||
|
|||||||
16
Makefile
16
Makefile
@@ -1,10 +1,24 @@
|
|||||||
.PHONY: check fmt clippy test kani build all image run
|
.PHONY: check fmt clippy test kani build all image run ci kernel-fmt kernel-clippy kernel-test
|
||||||
|
|
||||||
RISCV_TARGET = riscv64gc-unknown-none-elf
|
RISCV_TARGET = riscv64gc-unknown-none-elf
|
||||||
KERNEL_BIN = target/$(RISCV_TARGET)/release/adapter-riscv
|
KERNEL_BIN = target/$(RISCV_TARGET)/release/adapter-riscv
|
||||||
|
|
||||||
|
KERNEL_CRATES = -p types -p domain -p kernel -p platform
|
||||||
|
|
||||||
all: check fmt clippy test
|
all: check fmt clippy test
|
||||||
|
|
||||||
|
# Gate for kernel work: formatting, lints, unit tests, Kani proofs, RISC-V build.
|
||||||
|
ci: kernel-fmt kernel-clippy kernel-test kani build
|
||||||
|
|
||||||
|
kernel-fmt:
|
||||||
|
cargo fmt $(KERNEL_CRATES) -p adapter-riscv -- --check
|
||||||
|
|
||||||
|
kernel-clippy:
|
||||||
|
cargo clippy $(KERNEL_CRATES) --all-targets -- -D warnings
|
||||||
|
|
||||||
|
kernel-test:
|
||||||
|
cargo test $(KERNEL_CRATES)
|
||||||
|
|
||||||
check:
|
check:
|
||||||
cargo check --workspace
|
cargo check --workspace
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const CELL_SIZE: u16 = 24;
|
|||||||
const BG_COLOR: u32 = 0xFF202040;
|
const BG_COLOR: u32 = 0xFF202040;
|
||||||
const SNAKE_COLOR: u32 = 0xFF00FF00;
|
const SNAKE_COLOR: u32 = 0xFF00FF00;
|
||||||
const HEAD_COLOR: u32 = 0xFF00CC00;
|
const HEAD_COLOR: u32 = 0xFF00CC00;
|
||||||
const FOOD_COLOR: u32 = 0xFFFF0000;
|
pub(crate) const FOOD_COLOR: u32 = 0xFFFF0000;
|
||||||
const BORDER_COLOR: u32 = 0xFF505080;
|
const BORDER_COLOR: u32 = 0xFF505080;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
|||||||
@@ -28,5 +28,5 @@ fn food_is_rendered() {
|
|||||||
let count = render(&snake, &mut commands);
|
let count = render(&snake, &mut commands);
|
||||||
// Food is command index 2 (after bg and border)
|
// Food is command index 2 (after bg and border)
|
||||||
assert!(count > 2);
|
assert!(count > 2);
|
||||||
assert_eq!(commands[2].color, 0xFF0040);
|
assert_eq!(commands[2].color, crate::renderer::FOOD_COLOR);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,3 +5,6 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
types = { workspace = true }
|
types = { workspace = true }
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
unexpected_cfgs = { level = "warn", check-cfg = ["cfg(kani)"] }
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ use crate::{error::KernelError, resource::Resource};
|
|||||||
#[path = "tests.rs"]
|
#[path = "tests.rs"]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
const MAX_CAPS_PER_PROCESS: usize = 16;
|
// Sized so that every representable `CapSlot` indexes a real slot.
|
||||||
|
pub const MAX_CAPS_PER_PROCESS: usize = CapSlot::MAX as usize + 1;
|
||||||
|
|
||||||
pub struct Capability {
|
pub struct Capability {
|
||||||
resource: Resource,
|
resource: Resource,
|
||||||
@@ -76,3 +77,9 @@ impl CapabilityTable {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for CapabilityTable {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -113,12 +113,16 @@ impl EndpointTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_thread_from_all(&mut self, thread: ThreadId) {
|
pub fn remove_thread_from_all(&mut self, thread: ThreadId) {
|
||||||
for slot in &mut self.endpoints {
|
for ep in self.endpoints.iter_mut().flatten() {
|
||||||
if let Some(ep) = slot {
|
|
||||||
ep.remove_sender(thread);
|
ep.remove_sender(thread);
|
||||||
ep.remove_receiver(thread);
|
ep.remove_receiver(thread);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for EndpointTable {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,3 +44,9 @@ impl WaitQueue {
|
|||||||
self.count == 0
|
self.count == 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for WaitQueue {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -36,3 +36,9 @@ impl IrqRoutingTable {
|
|||||||
self.owners[irq.raw() as usize]
|
self.owners[irq.raw() as usize]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for IrqRoutingTable {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -104,12 +104,13 @@ impl RegionTable {
|
|||||||
base: PhysicalAddress,
|
base: PhysicalAddress,
|
||||||
) -> Option<(RegionId, &MemoryRegion)> {
|
) -> Option<(RegionId, &MemoryRegion)> {
|
||||||
for (i, slot) in self.regions.iter().enumerate() {
|
for (i, slot) in self.regions.iter().enumerate() {
|
||||||
if let Some(region) = slot {
|
if let Some(region) = slot
|
||||||
if region.owner() == owner && region.base() == base {
|
&& region.owner() == owner
|
||||||
|
&& region.base() == base
|
||||||
|
{
|
||||||
return Some((RegionId::from_raw(i as u16), region));
|
return Some((RegionId::from_raw(i as u16), region));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,3 +121,9 @@ impl RegionTable {
|
|||||||
.filter(move |r| r.owner() == process)
|
.filter(move |r| r.owner() == process)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for RegionTable {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,9 +12,7 @@ pub struct PagePool {
|
|||||||
impl PagePool {
|
impl PagePool {
|
||||||
pub fn new(base: PhysicalAddress, total_pages: usize) -> Self {
|
pub fn new(base: PhysicalAddress, total_pages: usize) -> Self {
|
||||||
let mut pages = [false; MAX_PAGES];
|
let mut pages = [false; MAX_PAGES];
|
||||||
for i in 0..total_pages {
|
pages[..total_pages].fill(true);
|
||||||
pages[i] = true;
|
|
||||||
}
|
|
||||||
Self {
|
Self {
|
||||||
pages,
|
pages,
|
||||||
base,
|
base,
|
||||||
|
|||||||
@@ -124,3 +124,9 @@ impl ProcessTable {
|
|||||||
.ok_or(KernelError::InvalidProcess(id))
|
.ok_or(KernelError::InvalidProcess(id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ProcessTable {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -47,13 +47,13 @@ impl MultiCoreScheduler {
|
|||||||
|
|
||||||
fn least_loaded_core(&self) -> Option<CoreId> {
|
fn least_loaded_core(&self) -> Option<CoreId> {
|
||||||
(0..self.num_cores)
|
(0..self.num_cores)
|
||||||
.map(|i| CoreId::from_raw(i))
|
.map(CoreId::from_raw)
|
||||||
.min_by_key(|&c| self.thread_count_on(c))
|
.min_by_key(|&c| self.thread_count_on(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn most_loaded_core(&self) -> Option<CoreId> {
|
fn most_loaded_core(&self) -> Option<CoreId> {
|
||||||
(0..self.num_cores)
|
(0..self.num_cores)
|
||||||
.map(|i| CoreId::from_raw(i))
|
.map(CoreId::from_raw)
|
||||||
.max_by_key(|&c| self.thread_count_on(c))
|
.max_by_key(|&c| self.thread_count_on(c))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,7 +69,7 @@ impl Scheduler for MultiCoreScheduler {
|
|||||||
let idx = thread.raw() as usize;
|
let idx = thread.raw() as usize;
|
||||||
if let Some(placement) = self.placements[idx].take() {
|
if let Some(placement) = self.placements[idx].take() {
|
||||||
let core_idx = placement.core.raw() as usize;
|
let core_idx = placement.core.raw() as usize;
|
||||||
self.cores[core_idx].remove(thread, placement.priority);
|
self.cores[core_idx].remove(thread);
|
||||||
if self.cores[core_idx].current() == Some(thread) {
|
if self.cores[core_idx].current() == Some(thread) {
|
||||||
self.cores[core_idx].clear_current();
|
self.cores[core_idx].clear_current();
|
||||||
}
|
}
|
||||||
@@ -85,15 +85,14 @@ impl Scheduler for MultiCoreScheduler {
|
|||||||
if let Some(placement) = &self.placements[idx] {
|
if let Some(placement) = &self.placements[idx] {
|
||||||
let current_priority = placement.priority;
|
let current_priority = placement.priority;
|
||||||
let core_idx = core.raw() as usize;
|
let core_idx = core.raw() as usize;
|
||||||
if let Some(highest) = self.cores[core_idx].highest_waiting_priority() {
|
if let Some(highest) = self.cores[core_idx].highest_waiting_priority()
|
||||||
if highest > current_priority {
|
&& highest > current_priority
|
||||||
if let Some(next) = self.cores[core_idx].pick_next() {
|
&& let Some(next) = self.cores[core_idx].pick_next()
|
||||||
|
{
|
||||||
self.cores[core_idx].add(current, current_priority);
|
self.cores[core_idx].add(current, current_priority);
|
||||||
return SchedulerDecision::Preempt(next);
|
return SchedulerDecision::Preempt(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
SchedulerDecision::Continue
|
SchedulerDecision::Continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,12 +29,12 @@ impl PerCoreScheduler {
|
|||||||
self.queue.add(thread, priority);
|
self.queue.add(thread, priority);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(&mut self, thread: ThreadId, priority: Priority) {
|
pub fn remove(&mut self, thread: ThreadId) {
|
||||||
self.queue.remove(thread, priority);
|
self.queue.remove(thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pick_next(&mut self) -> Option<ThreadId> {
|
pub fn pick_next(&mut self) -> Option<ThreadId> {
|
||||||
let (thread, _priority) = self.queue.next()?;
|
let (thread, _priority) = self.queue.pop_highest()?;
|
||||||
self.current = Some(thread);
|
self.current = Some(thread);
|
||||||
Some(thread)
|
Some(thread)
|
||||||
}
|
}
|
||||||
@@ -56,6 +56,10 @@ impl PerCoreScheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.queue.bitmap().count_ones() as usize
|
self.queue.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.queue.is_empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,71 +2,42 @@ use types::bounded::Priority;
|
|||||||
use types::id::ThreadId;
|
use types::id::ThreadId;
|
||||||
|
|
||||||
const NUM_PRIORITIES: usize = 32;
|
const NUM_PRIORITIES: usize = 32;
|
||||||
const MAX_THREADS_PER_PRIORITY: usize = 16;
|
const MAX_THREADS: usize = crate::thread::MAX_THREADS;
|
||||||
|
|
||||||
struct RunList {
|
#[derive(Clone, Copy)]
|
||||||
threads: [Option<ThreadId>; MAX_THREADS_PER_PRIORITY],
|
struct Links {
|
||||||
head: usize,
|
prev: Option<ThreadId>,
|
||||||
tail: usize,
|
next: Option<ThreadId>,
|
||||||
count: u8,
|
queued_at: Option<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunList {
|
#[derive(Clone, Copy)]
|
||||||
fn new() -> Self {
|
struct ListEnds {
|
||||||
Self {
|
head: Option<ThreadId>,
|
||||||
threads: [None; MAX_THREADS_PER_PRIORITY],
|
tail: Option<ThreadId>,
|
||||||
head: 0,
|
|
||||||
tail: 0,
|
|
||||||
count: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push(&mut self, thread: ThreadId) -> bool {
|
|
||||||
match self.threads.iter_mut().find(|s| s.is_none()) {
|
|
||||||
Some(slot) => {
|
|
||||||
*slot = Some(thread);
|
|
||||||
self.count += 1;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
None => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pop(&mut self) -> Option<ThreadId> {
|
|
||||||
if self.count == 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let slot = self.threads.iter_mut().find(|s| s.is_some())?;
|
|
||||||
let thread = slot.take();
|
|
||||||
self.count -= 1;
|
|
||||||
thread
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove(&mut self, thread: ThreadId) -> bool {
|
|
||||||
for slot in &mut self.threads {
|
|
||||||
if *slot == Some(thread) {
|
|
||||||
*slot = None;
|
|
||||||
self.count -= 1;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_empty(&self) -> bool {
|
|
||||||
self.count == 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ready queue: one FIFO per priority level, threaded through an
|
||||||
|
/// intrusive doubly linked list indexed by thread id. A thread is in at
|
||||||
|
/// most one list, so capacity never runs out and every op is O(1).
|
||||||
pub struct PriorityQueue {
|
pub struct PriorityQueue {
|
||||||
queues: [RunList; NUM_PRIORITIES],
|
lists: [ListEnds; NUM_PRIORITIES],
|
||||||
|
links: [Links; MAX_THREADS],
|
||||||
bitmap: u32,
|
bitmap: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PriorityQueue {
|
impl PriorityQueue {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
queues: core::array::from_fn(|_| RunList::new()),
|
lists: [ListEnds {
|
||||||
|
head: None,
|
||||||
|
tail: None,
|
||||||
|
}; NUM_PRIORITIES],
|
||||||
|
links: [Links {
|
||||||
|
prev: None,
|
||||||
|
next: None,
|
||||||
|
queued_at: None,
|
||||||
|
}; MAX_THREADS],
|
||||||
bitmap: 0,
|
bitmap: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,34 +46,84 @@ impl PriorityQueue {
|
|||||||
self.bitmap
|
self.bitmap
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add(&mut self, thread: ThreadId, priority: Priority) {
|
pub fn len(&self) -> usize {
|
||||||
let idx = priority.value() as usize;
|
self.links.iter().filter(|l| l.queued_at.is_some()).count()
|
||||||
let added = self.queues[idx].push(thread);
|
|
||||||
debug_assert!(added, "scheduler run list full for priority {}", idx);
|
|
||||||
if added {
|
|
||||||
self.bitmap |= 1 << idx;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(&mut self, thread: ThreadId, priority: Priority) {
|
pub fn contains(&self, thread: ThreadId) -> bool {
|
||||||
|
self.link(thread).is_some_and(|l| l.queued_at.is_some())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn link(&self, thread: ThreadId) -> Option<&Links> {
|
||||||
|
self.links.get(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 idx = priority.value() as usize;
|
||||||
self.queues[idx].remove(thread);
|
let t = thread.raw() as usize;
|
||||||
if self.queues[idx].is_empty() {
|
if t >= MAX_THREADS || idx >= NUM_PRIORITIES || self.links[t].queued_at.is_some() {
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
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()
|
||||||
|
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 next {
|
||||||
|
Some(n) => self.links[n.raw() as usize].prev = prev,
|
||||||
|
None => self.lists[idx].tail = prev,
|
||||||
|
}
|
||||||
|
self.links[t] = Links {
|
||||||
|
prev: None,
|
||||||
|
next: None,
|
||||||
|
queued_at: None,
|
||||||
|
};
|
||||||
|
if self.lists[idx].head.is_none() {
|
||||||
self.bitmap &= !(1 << idx);
|
self.bitmap &= !(1 << idx);
|
||||||
}
|
}
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next(&mut self) -> Option<(ThreadId, Priority)> {
|
fn pop_level(&mut self, idx: usize) -> Option<(ThreadId, Priority)> {
|
||||||
|
let thread = self.lists[idx].head?;
|
||||||
|
self.remove(thread);
|
||||||
|
Some((thread, Priority::new(idx as u8)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop_highest(&mut self) -> Option<(ThreadId, Priority)> {
|
||||||
if self.bitmap == 0 {
|
if self.bitmap == 0 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
// highest set bit = highest priority
|
// highest set bit = highest priority
|
||||||
let idx = 31 - self.bitmap.leading_zeros() as usize;
|
self.pop_level(31 - self.bitmap.leading_zeros() as usize)
|
||||||
let thread = self.queues[idx].pop()?;
|
|
||||||
if self.queues[idx].is_empty() {
|
|
||||||
self.bitmap &= !(1 << idx);
|
|
||||||
}
|
|
||||||
Some((thread, Priority::new(idx as u8)?))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn highest_priority(&self) -> Option<Priority> {
|
pub fn highest_priority(&self) -> Option<Priority> {
|
||||||
@@ -117,15 +138,16 @@ impl PriorityQueue {
|
|||||||
if self.bitmap == 0 {
|
if self.bitmap == 0 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let idx = self.bitmap.trailing_zeros() as usize;
|
self.pop_level(self.bitmap.trailing_zeros() as usize)
|
||||||
let thread = self.queues[idx].pop()?;
|
|
||||||
if self.queues[idx].is_empty() {
|
|
||||||
self.bitmap &= !(1 << idx);
|
|
||||||
}
|
|
||||||
Some((thread, Priority::new(idx as u8)?))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.bitmap == 0
|
self.bitmap == 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for PriorityQueue {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ fn prio(value: u8) -> Priority {
|
|||||||
#[test]
|
#[test]
|
||||||
fn empty_queue_returns_none() {
|
fn empty_queue_returns_none() {
|
||||||
let mut q = PriorityQueue::new();
|
let mut q = PriorityQueue::new();
|
||||||
assert!(q.next().is_none());
|
assert!(q.pop_highest().is_none());
|
||||||
assert!(q.is_empty());
|
assert!(q.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ fn single_thread() {
|
|||||||
let mut q = PriorityQueue::new();
|
let mut q = PriorityQueue::new();
|
||||||
q.add(thread(1), prio(10));
|
q.add(thread(1), prio(10));
|
||||||
|
|
||||||
let (t, p) = q.next().unwrap();
|
let (t, p) = q.pop_highest().unwrap();
|
||||||
assert_eq!(t, thread(1));
|
assert_eq!(t, thread(1));
|
||||||
assert_eq!(p, prio(10));
|
assert_eq!(p, prio(10));
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ fn highest_priority_runs_first() {
|
|||||||
q.add(thread(2), prio(20));
|
q.add(thread(2), prio(20));
|
||||||
q.add(thread(3), prio(10));
|
q.add(thread(3), prio(10));
|
||||||
|
|
||||||
let (t, _) = q.next().unwrap();
|
let (t, _) = q.pop_highest().unwrap();
|
||||||
assert_eq!(t, thread(2));
|
assert_eq!(t, thread(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,10 +54,10 @@ fn same_priority_is_fifo() {
|
|||||||
q.add(thread(2), prio(10));
|
q.add(thread(2), prio(10));
|
||||||
q.add(thread(3), prio(10));
|
q.add(thread(3), prio(10));
|
||||||
|
|
||||||
assert_eq!(q.next().unwrap().0, thread(1));
|
assert_eq!(q.pop_highest().unwrap().0, thread(1));
|
||||||
assert_eq!(q.next().unwrap().0, thread(2));
|
assert_eq!(q.pop_highest().unwrap().0, thread(2));
|
||||||
assert_eq!(q.next().unwrap().0, thread(3));
|
assert_eq!(q.pop_highest().unwrap().0, thread(3));
|
||||||
assert!(q.next().is_none());
|
assert!(q.pop_highest().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -66,18 +66,18 @@ fn remove_thread() {
|
|||||||
q.add(thread(1), prio(10));
|
q.add(thread(1), prio(10));
|
||||||
q.add(thread(2), prio(10));
|
q.add(thread(2), prio(10));
|
||||||
|
|
||||||
q.remove(thread(1), prio(10));
|
q.remove(thread(1));
|
||||||
|
|
||||||
let (t, _) = q.next().unwrap();
|
let (t, _) = q.pop_highest().unwrap();
|
||||||
assert_eq!(t, thread(2));
|
assert_eq!(t, thread(2));
|
||||||
assert!(q.next().is_none());
|
assert!(q.pop_highest().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn remove_clears_bitmap_when_empty() {
|
fn remove_clears_bitmap_when_empty() {
|
||||||
let mut q = PriorityQueue::new();
|
let mut q = PriorityQueue::new();
|
||||||
q.add(thread(1), prio(10));
|
q.add(thread(1), prio(10));
|
||||||
q.remove(thread(1), prio(10));
|
q.remove(thread(1));
|
||||||
|
|
||||||
assert!(q.is_empty());
|
assert!(q.is_empty());
|
||||||
}
|
}
|
||||||
@@ -89,9 +89,9 @@ fn drain_all_priorities() {
|
|||||||
q.add(thread(2), prio(15));
|
q.add(thread(2), prio(15));
|
||||||
q.add(thread(3), prio(31));
|
q.add(thread(3), prio(31));
|
||||||
|
|
||||||
assert_eq!(q.next().unwrap().0, thread(3)); // highest first
|
assert_eq!(q.pop_highest().unwrap().0, thread(3)); // highest first
|
||||||
assert_eq!(q.next().unwrap().0, thread(2));
|
assert_eq!(q.pop_highest().unwrap().0, thread(2));
|
||||||
assert_eq!(q.next().unwrap().0, thread(1)); // lowest last
|
assert_eq!(q.pop_highest().unwrap().0, thread(1)); // lowest last
|
||||||
assert!(q.is_empty());
|
assert!(q.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ fn highest_priority_peeks_without_popping() {
|
|||||||
q.add(thread(1), prio(5));
|
q.add(thread(1), prio(5));
|
||||||
q.add(thread(2), prio(20));
|
q.add(thread(2), prio(20));
|
||||||
assert_eq!(q.highest_priority(), Some(prio(20)));
|
assert_eq!(q.highest_priority(), Some(prio(20)));
|
||||||
assert_eq!(q.next().unwrap().0, thread(2));
|
assert_eq!(q.pop_highest().unwrap().0, thread(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -154,7 +154,7 @@ fn steal_lowest_takes_lowest_priority_thread() {
|
|||||||
let (t, p) = q.steal_lowest().unwrap();
|
let (t, p) = q.steal_lowest().unwrap();
|
||||||
assert_eq!(t, thread(1));
|
assert_eq!(t, thread(1));
|
||||||
assert_eq!(p, prio(5));
|
assert_eq!(p, prio(5));
|
||||||
assert_eq!(q.next().unwrap().0, thread(2));
|
assert_eq!(q.pop_highest().unwrap().0, thread(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -240,3 +240,47 @@ fn multi_threads_on_different_cores() {
|
|||||||
assert_eq!(s.next(core(0)), Some(thread(1)));
|
assert_eq!(s.next(core(0)), Some(thread(1)));
|
||||||
assert_eq!(s.next(core(1)), Some(thread(2)));
|
assert_eq!(s.next(core(1)), Some(thread(2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn requeued_thread_goes_behind_same_priority_peers() {
|
||||||
|
let mut q = PriorityQueue::new();
|
||||||
|
let p = Priority::new(5).unwrap();
|
||||||
|
q.add(thread(1), p);
|
||||||
|
q.add(thread(2), p);
|
||||||
|
|
||||||
|
assert_eq!(q.pop_highest().unwrap().0, thread(1));
|
||||||
|
q.add(thread(1), p); // preempted, back in the run list
|
||||||
|
|
||||||
|
assert_eq!(q.pop_highest().unwrap().0, thread(2));
|
||||||
|
assert_eq!(q.pop_highest().unwrap().0, thread(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn round_robin_survives_wraparound() {
|
||||||
|
let mut q = PriorityQueue::new();
|
||||||
|
let p = Priority::new(5).unwrap();
|
||||||
|
q.add(thread(1), p);
|
||||||
|
q.add(thread(2), p);
|
||||||
|
q.add(thread(3), p);
|
||||||
|
for _ in 0..40 {
|
||||||
|
let (a, _) = q.pop_highest().unwrap();
|
||||||
|
q.add(a, p);
|
||||||
|
}
|
||||||
|
// 40 rotations of 3 threads: 40 % 3 == 1, so thread 2 is at the head
|
||||||
|
assert_eq!(q.pop_highest().unwrap().0, thread(2));
|
||||||
|
assert_eq!(q.pop_highest().unwrap().0, thread(3));
|
||||||
|
assert_eq!(q.pop_highest().unwrap().0, thread(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn removed_thread_is_skipped_and_order_kept() {
|
||||||
|
let mut q = PriorityQueue::new();
|
||||||
|
let p = Priority::new(5).unwrap();
|
||||||
|
q.add(thread(1), p);
|
||||||
|
q.add(thread(2), p);
|
||||||
|
q.add(thread(3), p);
|
||||||
|
q.remove(thread(2));
|
||||||
|
assert_eq!(q.pop_highest().unwrap().0, thread(1));
|
||||||
|
assert_eq!(q.pop_highest().unwrap().0, thread(3));
|
||||||
|
assert!(q.pop_highest().is_none());
|
||||||
|
}
|
||||||
|
|||||||
@@ -303,3 +303,9 @@ impl ThreadTable {
|
|||||||
.filter(move |t| t.process() == process)
|
.filter(move |t| t.process() == process)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ThreadTable {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ impl ElfInfo {
|
|||||||
pub fn total_pages(&self) -> usize {
|
pub fn total_pages(&self) -> usize {
|
||||||
let mut total = 0;
|
let mut total = 0;
|
||||||
for seg in self.segments() {
|
for seg in self.segments() {
|
||||||
total += (seg.mem_size + 4095) / 4096;
|
total += seg.mem_size.div_ceil(4096);
|
||||||
}
|
}
|
||||||
total
|
total
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,3 +7,6 @@ edition = "2024"
|
|||||||
types = { workspace = true }
|
types = { workspace = true }
|
||||||
domain = { workspace = true }
|
domain = { workspace = true }
|
||||||
platform = { workspace = true }
|
platform = { workspace = true }
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
unexpected_cfgs = { level = "warn", check-cfg = ["cfg(kani)"] }
|
||||||
|
|||||||
@@ -196,22 +196,22 @@ impl<P: Platform> Kernel<P> {
|
|||||||
if current == tid {
|
if current == tid {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if let Ok(t) = self.threads.get_mut(current) {
|
if let Ok(t) = self.threads.get_mut(current)
|
||||||
if matches!(t.state(), ThreadState::Running(_)) {
|
&& matches!(t.state(), ThreadState::Running(_))
|
||||||
|
{
|
||||||
let priority = t.priority();
|
let priority = t.priority();
|
||||||
let _ = t.preempt();
|
let _ = t.preempt();
|
||||||
self.scheduler.add(current, priority, core);
|
self.scheduler.add(current, priority, core);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(t) = self.threads.get(tid) {
|
if let Ok(t) = self.threads.get(tid)
|
||||||
if matches!(t.state(), ThreadState::Ready) {
|
&& matches!(t.state(), ThreadState::Ready)
|
||||||
|
{
|
||||||
self.scheduler.next(core);
|
self.scheduler.next(core);
|
||||||
let _ = self.threads.get_mut(tid).map(|t| t.set_running(core));
|
let _ = self.threads.get_mut(tid).map(|t| t.set_running(core));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn caller(&self) -> Result<(ThreadId, ProcessId), KernelError> {
|
fn caller(&self) -> Result<(ThreadId, ProcessId), KernelError> {
|
||||||
let core = self.platform.core_id();
|
let core = self.platform.core_id();
|
||||||
@@ -265,8 +265,7 @@ impl<P: Platform> Kernel<P> {
|
|||||||
|
|
||||||
pub(crate) fn cleanup_threads(&mut self, thread_ids: &[Option<ThreadId>]) {
|
pub(crate) fn cleanup_threads(&mut self, thread_ids: &[Option<ThreadId>]) {
|
||||||
use domain::scheduler::Scheduler;
|
use domain::scheduler::Scheduler;
|
||||||
for slot in thread_ids {
|
for tid in thread_ids.iter().flatten() {
|
||||||
if let Some(tid) = slot {
|
|
||||||
self.endpoints.remove_thread_from_all(*tid);
|
self.endpoints.remove_thread_from_all(*tid);
|
||||||
self.cleanup_wait_any(*tid);
|
self.cleanup_wait_any(*tid);
|
||||||
self.scheduler.remove(*tid);
|
self.scheduler.remove(*tid);
|
||||||
@@ -275,16 +274,10 @@ impl<P: Platform> Kernel<P> {
|
|||||||
let _ = self.threads.destroy(*tid);
|
let _ = self.threads.destroy(*tid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn cleanup_wait_any(&mut self, tid: ThreadId) {
|
pub(crate) fn cleanup_wait_any(&mut self, tid: ThreadId) {
|
||||||
if let Some(wait_set) = self.wait_sets[tid.raw() as usize].take() {
|
if self.wait_sets[tid.raw() as usize].take().is_some() {
|
||||||
for ep_slot in wait_set.endpoints() {
|
|
||||||
if let Some(_slot) = ep_slot {
|
|
||||||
self.endpoints.remove_thread_from_all(tid);
|
self.endpoints.remove_thread_from_all(tid);
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,12 +305,12 @@ impl<P: Platform> Kernel<P> {
|
|||||||
.endpoints
|
.endpoints
|
||||||
.get(*id)
|
.get(*id)
|
||||||
.map(|ep| *ep.generation())
|
.map(|ep| *ep.generation())
|
||||||
.unwrap_or_else(|| Generation::initial()),
|
.unwrap_or_else(Generation::initial),
|
||||||
Resource::Memory(id) => self
|
Resource::Memory(id) => self
|
||||||
.regions
|
.regions
|
||||||
.get(*id)
|
.get(*id)
|
||||||
.map(|r| *r.generation())
|
.map(|r| *r.generation())
|
||||||
.unwrap_or_else(|| Generation::initial()),
|
.unwrap_or_else(Generation::initial),
|
||||||
_ => Generation::initial(),
|
_ => Generation::initial(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,12 +42,12 @@ impl<P: Platform> Kernel<P> {
|
|||||||
return SyscallOutcome::Complete(SyscallResult::Error(e));
|
return SyscallOutcome::Complete(SyscallResult::Error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let domain::resource::Resource::Memory(region_id) = &resource {
|
if let domain::resource::Resource::Memory(region_id) = &resource
|
||||||
if let Some(region) = self.regions.get(*region_id) {
|
&& let Some(region) = self.regions.get(*region_id)
|
||||||
|
{
|
||||||
self.platform
|
self.platform
|
||||||
.map_shared(target_pid, region.base(), region.size(), restricted);
|
.map_shared(target_pid, region.base(), region.size(), restricted);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
SyscallOutcome::Complete(SyscallResult::Ok)
|
SyscallOutcome::Complete(SyscallResult::Ok)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,10 +15,11 @@ impl<P: Platform> Kernel<P> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Try to deliver fault to a fault handler endpoint (if configured)
|
// Try to deliver fault to a fault handler endpoint (if configured)
|
||||||
if let Ok(process) = self.processes.get(pid) {
|
if let Ok(process) = self.processes.get(pid)
|
||||||
if let Some(handler_slot) = process.fault_handler() {
|
&& let Some(handler_slot) = process.fault_handler()
|
||||||
if let Ok(cap) = process.capabilities().get(handler_slot) {
|
&& let Ok(cap) = process.capabilities().get(handler_slot)
|
||||||
if let domain::resource::Resource::Endpoint(ep_id) = cap.resource() {
|
&& let domain::resource::Resource::Endpoint(ep_id) = cap.resource()
|
||||||
|
{
|
||||||
let msg = domain::message::Message::from_words([
|
let msg = domain::message::Message::from_words([
|
||||||
pid.raw() as usize,
|
pid.raw() as usize,
|
||||||
fault.code(),
|
fault.code(),
|
||||||
@@ -26,9 +27,10 @@ impl<P: Platform> Kernel<P> {
|
|||||||
fault_pc,
|
fault_pc,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if let Some(ep) = self.endpoints.get_mut(*ep_id) {
|
if let Some(ep) = self.endpoints.get_mut(*ep_id)
|
||||||
if ep.has_waiting_receiver() {
|
&& ep.has_waiting_receiver()
|
||||||
if let Some(receiver_tid) = ep.dequeue_receiver() {
|
&& let Some(receiver_tid) = ep.dequeue_receiver()
|
||||||
|
{
|
||||||
self.pending_results[receiver_tid.raw() as usize] =
|
self.pending_results[receiver_tid.raw() as usize] =
|
||||||
Some(crate::SyscallResult::Message(msg));
|
Some(crate::SyscallResult::Message(msg));
|
||||||
|
|
||||||
@@ -41,11 +43,6 @@ impl<P: Platform> Kernel<P> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.destroy_process(pid);
|
self.destroy_process(pid);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ impl<P: Platform> Kernel<P> {
|
|||||||
message.is_some(),
|
message.is_some(),
|
||||||
"sender in wait queue had no pending message"
|
"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);
|
self.pending_results[sender_tid.raw() as usize] = Some(SyscallResult::Ok);
|
||||||
|
|
||||||
@@ -289,10 +289,9 @@ impl<P: Platform> Kernel<P> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
for ep_slot in sources.endpoints() {
|
for slot in sources.endpoints().iter().flatten() {
|
||||||
if let Some(slot) = ep_slot {
|
let ep_id = match self.check_cap(caller_pid, *slot, types::permission::Permission::Read)
|
||||||
let ep_id =
|
{
|
||||||
match self.check_cap(caller_pid, *slot, types::permission::Permission::Read) {
|
|
||||||
Ok(cap) => match cap.resource() {
|
Ok(cap) => match cap.resource() {
|
||||||
domain::resource::Resource::Endpoint(id) => *id,
|
domain::resource::Resource::Endpoint(id) => *id,
|
||||||
_ => continue,
|
_ => continue,
|
||||||
@@ -300,45 +299,38 @@ impl<P: Platform> Kernel<P> {
|
|||||||
Err(_) => continue,
|
Err(_) => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(ep) = self.endpoints.get_mut(ep_id) {
|
if let Some(ep) = self.endpoints.get_mut(ep_id)
|
||||||
if ep.has_waiting_sender() {
|
&& ep.has_waiting_sender()
|
||||||
|
{
|
||||||
return SyscallOutcome::Complete(SyscallResult::WakeReason(
|
return SyscallOutcome::Complete(SyscallResult::WakeReason(
|
||||||
domain::syscall::WakeReason::MessageReady(*slot),
|
domain::syscall::WakeReason::MessageReady(*slot),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for irq_slot in sources.interrupts() {
|
for irq_slot in sources.interrupts() {
|
||||||
if let Some(slot) = irq_slot {
|
if let Some(slot) = irq_slot
|
||||||
if let Ok(cap) =
|
&& let Ok(cap) =
|
||||||
self.check_cap(caller_pid, *slot, types::permission::Permission::Read)
|
self.check_cap(caller_pid, *slot, types::permission::Permission::Read)
|
||||||
|
&& let domain::resource::Resource::Interrupt(irq_id) = cap.resource()
|
||||||
|
&& self.irqs.owner(*irq_id).is_some()
|
||||||
{
|
{
|
||||||
if let domain::resource::Resource::Interrupt(irq_id) = cap.resource() {
|
|
||||||
if self.irqs.owner(*irq_id).is_some() {
|
|
||||||
return SyscallOutcome::Complete(SyscallResult::WakeReason(
|
return SyscallOutcome::Complete(SyscallResult::WakeReason(
|
||||||
domain::syscall::WakeReason::InterruptFired(*slot),
|
domain::syscall::WakeReason::InterruptFired(*slot),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for ep_slot in sources.endpoints() {
|
for ep_slot in sources.endpoints() {
|
||||||
if let Some(slot) = ep_slot {
|
if let Some(slot) = ep_slot
|
||||||
if let Ok(cap) =
|
&& let Ok(cap) =
|
||||||
self.check_cap(caller_pid, *slot, types::permission::Permission::Read)
|
self.check_cap(caller_pid, *slot, types::permission::Permission::Read)
|
||||||
|
&& let domain::resource::Resource::Endpoint(id) = cap.resource()
|
||||||
|
&& let Some(ep) = self.endpoints.get_mut(*id)
|
||||||
{
|
{
|
||||||
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 _ = ep.enqueue_receiver(caller_tid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.wait_sets[caller_tid.raw() as usize] = Some(sources);
|
self.wait_sets[caller_tid.raw() as usize] = Some(sources);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use domain::process::MAX_THREADS_PER_PROCESS;
|
|
||||||
use platform::Platform;
|
use platform::Platform;
|
||||||
|
|
||||||
use super::{SyscallOutcome, SyscallResult};
|
use super::{SyscallOutcome, SyscallResult};
|
||||||
|
|||||||
@@ -197,3 +197,18 @@ fn revoke_syscall_without_revoke_permission_fails() {
|
|||||||
SyscallOutcome::Complete(SyscallResult::Error(_))
|
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 super::*;
|
||||||
use crate::{SyscallOutcome, SyscallResult};
|
use crate::SyscallOutcome;
|
||||||
use domain::syscall::Syscall;
|
use domain::syscall::Syscall;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -21,5 +21,5 @@ fn exit_thread_last_thread_destroys_process() {
|
|||||||
let (mut k, pid) = kernel_with_process();
|
let (mut k, pid) = kernel_with_process();
|
||||||
k.handle_syscall(Syscall::ExitThread);
|
k.handle_syscall(Syscall::ExitThread);
|
||||||
// Process should be gone since it had only one thread
|
// Process should be gone since it had only one thread
|
||||||
assert!(k.process_exists(pid) == false);
|
assert!(!k.process_exists(pid));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,22 +65,22 @@ impl Kernel<MockPlatform> {
|
|||||||
if current == tid {
|
if current == tid {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if let Ok(t) = self.threads.get_mut(current) {
|
if let Ok(t) = self.threads.get_mut(current)
|
||||||
if matches!(t.state(), ThreadState::Running(_)) {
|
&& matches!(t.state(), ThreadState::Running(_))
|
||||||
|
{
|
||||||
let priority = t.priority();
|
let priority = t.priority();
|
||||||
let _ = t.preempt();
|
let _ = t.preempt();
|
||||||
self.scheduler.add(current, priority, core);
|
self.scheduler.add(current, priority, core);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(t) = self.threads.get(tid) {
|
if let Ok(t) = self.threads.get(tid)
|
||||||
if matches!(t.state(), ThreadState::Ready) {
|
&& matches!(t.state(), ThreadState::Ready)
|
||||||
|
{
|
||||||
self.scheduler.next(core);
|
self.scheduler.next(core);
|
||||||
let _ = self.threads.get_mut(tid).map(|t| t.set_running(core));
|
let _ = self.threads.get_mut(tid).map(|t| t.set_running(core));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_endpoint_for_test(&mut self) -> EndpointId {
|
pub fn create_endpoint_for_test(&mut self) -> EndpointId {
|
||||||
self.endpoints.create().expect("create endpoint")
|
self.endpoints.create().expect("create endpoint")
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ fn send_without_receiver_blocks_caller() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn receive_without_sender_blocks_caller() {
|
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
|
// Switch to thread B to call Receive
|
||||||
k.switch_to_thread_for_test(tid_b);
|
k.switch_to_thread_for_test(tid_b);
|
||||||
@@ -83,7 +83,7 @@ fn receive_without_sender_blocks_caller() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn send_with_waiting_receiver_delivers_and_switches() {
|
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);
|
let tid_a = ThreadId::from_raw(0);
|
||||||
|
|
||||||
// Thread B calls Receive first (blocks)
|
// Thread B calls Receive first (blocks)
|
||||||
@@ -108,8 +108,8 @@ fn send_with_waiting_receiver_delivers_and_switches() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn receive_with_waiting_sender_delivers_immediately() {
|
fn receive_with_waiting_sender_delivers_immediately() {
|
||||||
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);
|
let _tid_a = ThreadId::from_raw(0);
|
||||||
|
|
||||||
// Thread A sends first (blocks)
|
// Thread A sends first (blocks)
|
||||||
let msg = Message::from_words([99, 0, 0, 0]);
|
let msg = Message::from_words([99, 0, 0, 0]);
|
||||||
@@ -184,7 +184,7 @@ fn ack_interrupt_returns_ok() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn wait_any_with_ready_source_returns_immediately() {
|
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);
|
let tid_a = ThreadId::from_raw(0);
|
||||||
|
|
||||||
// Thread B sends a message (blocks since no receiver)
|
// Thread B sends a message (blocks since no receiver)
|
||||||
@@ -206,7 +206,7 @@ fn wait_any_with_ready_source_returns_immediately() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn wait_any_with_no_ready_source_blocks() {
|
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 wait_set = WaitSet::new([Some(cap_a), None, None, None], [None, None, None, None]);
|
||||||
let outcome = k.handle_syscall(Syscall::WaitAny { sources: wait_set });
|
let outcome = k.handle_syscall(Syscall::WaitAny { sources: wait_set });
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ impl MockPlatform {
|
|||||||
impl Platform for MockPlatform {
|
impl Platform for MockPlatform {
|
||||||
fn configure_protection(&mut self, _process: ProcessId, _regions: &[MemoryRegion]) {}
|
fn configure_protection(&mut self, _process: ProcessId, _regions: &[MemoryRegion]) {}
|
||||||
fn enable_user_mode(&mut self, _entry: EntryPoint, _stack: StackPointer) -> ! {
|
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 set_timer(&mut self, _duration: TimerDuration) {}
|
||||||
fn current_ticks(&self) -> Ticks {
|
fn current_ticks(&self) -> Ticks {
|
||||||
@@ -65,18 +65,19 @@ impl Platform for MockPlatform {
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
fn halt(&self) -> ! {
|
fn halt(&self) -> ! {
|
||||||
loop {}
|
panic!("halt")
|
||||||
}
|
}
|
||||||
fn debug_putchar(&self, _byte: u8) {}
|
fn debug_putchar(&self, _byte: u8) {}
|
||||||
|
|
||||||
fn translate_va(&self, process: ProcessId, va: VirtualAddress) -> Option<PhysicalAddress> {
|
fn translate_va(&self, process: ProcessId, va: VirtualAddress) -> Option<PhysicalAddress> {
|
||||||
for m in &self.mappings {
|
for m in &self.mappings {
|
||||||
if let Some(m) = m {
|
if let Some(m) = m
|
||||||
if m.process.raw() == process.raw() && m.va == va.raw() {
|
&& m.process.raw() == process.raw()
|
||||||
|
&& m.va == va.raw()
|
||||||
|
{
|
||||||
return Some(PhysicalAddress::from_raw(m.pa));
|
return Some(PhysicalAddress::from_raw(m.pa));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,16 +12,16 @@ fn tick_continues_when_time_remaining() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tick_preempts_when_time_expired_and_other_thread_ready() {
|
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 mock = MockPlatform::new(1);
|
||||||
let pool = PagePool::new(PhysicalAddress::from_raw(0x8000_0000), 1024);
|
let pool = PagePool::new(PhysicalAddress::from_raw(0x8000_0000), 1024);
|
||||||
let mut k = Kernel::new(mock, pool);
|
let mut k = Kernel::new(mock, pool);
|
||||||
|
|
||||||
// Boot with quantum=2 so it expires after 2 ticks
|
// 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
|
// 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
|
// First tick: time remaining
|
||||||
let d1 = k.handle_tick(CoreId::from_raw(0));
|
let d1 = k.handle_tick(CoreId::from_raw(0));
|
||||||
|
|||||||
@@ -4,3 +4,6 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
unexpected_cfgs = { level = "warn", check-cfg = ["cfg(kani)"] }
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ macro_rules! bounded {
|
|||||||
pub const MAX: $inner = $max;
|
pub const MAX: $inner = $max;
|
||||||
|
|
||||||
pub fn new(value: $inner) -> Option<Self> {
|
pub fn new(value: $inner) -> Option<Self> {
|
||||||
if value >= Self::MIN && value <= Self::MAX {
|
if (Self::MIN..=Self::MAX).contains(&value) {
|
||||||
Some(Self(value))
|
Some(Self(value))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|||||||
Reference in New Issue
Block a user