perf(kernel): linked cap tree, translate-based page lookup, next-fit frames/heap, targeted dequeue; host benches
This commit is contained in:
@@ -4,7 +4,7 @@ pub mod page_table;
|
|||||||
pub use allocator::init_allocator;
|
pub use allocator::init_allocator;
|
||||||
pub use page_table::{
|
pub use page_table::{
|
||||||
PAGE_SIZE, PageTable, Sv39Flags, USER_VA_END, USER_VA_START, activate, active_root, check_wx,
|
PAGE_SIZE, PageTable, Sv39Flags, USER_VA_END, USER_VA_START, activate, active_root, check_wx,
|
||||||
create_user_root, init_kernel_page_table, map_user_page, unmap_user_page,
|
create_user_root, init_kernel_page_table, map_user_page, translate_user, unmap_user_page,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The kernel lives in the upper half of the address space: all of RAM and
|
/// The kernel lives in the upper half of the address space: all of RAM and
|
||||||
|
|||||||
@@ -405,6 +405,16 @@ pub fn map_user_page(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The physical page `va` maps to in the address space at `root_pa`.
|
||||||
|
pub fn translate_user(root_pa: usize, va: usize) -> Option<usize> {
|
||||||
|
if !in_user_window(va) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let l0 = user_leaf_table(root_pa, va, None).ok()?;
|
||||||
|
let entry = l0.entries[vpn(va, 0)];
|
||||||
|
entry.is_valid().then(|| (entry.ppn() << 12) as usize)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn unmap_user_page(root_pa: usize, va: usize) {
|
pub fn unmap_user_page(root_pa: usize, va: usize) {
|
||||||
if !in_user_window(va) {
|
if !in_user_window(va) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -103,6 +103,11 @@ impl Platform for RiscVPlatform {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn translate(&self, process: ProcessId, va: VirtualAddress) -> Option<PhysicalAddress> {
|
||||||
|
let root = self.root(process)?;
|
||||||
|
mmu::translate_user(root, va.raw()).map(PhysicalAddress::from_raw)
|
||||||
|
}
|
||||||
|
|
||||||
fn zero_frame(&mut self, pa: PhysicalAddress) {
|
fn zero_frame(&mut self, pa: PhysicalAddress) {
|
||||||
// SAFETY: pool frames are RAM, mapped at `phys_to_virt`, and the
|
// SAFETY: pool frames are RAM, mapped at `phys_to_virt`, and the
|
||||||
// kernel only zeroes frames it has just allocated.
|
// kernel only zeroes frames it has just allocated.
|
||||||
|
|||||||
@@ -5,11 +5,16 @@ use types::{
|
|||||||
|
|
||||||
pub mod authority;
|
pub mod authority;
|
||||||
pub mod derivation;
|
pub mod derivation;
|
||||||
|
pub mod tree;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "derivation_tests.rs"]
|
#[path = "derivation_tests.rs"]
|
||||||
mod derivation_tests;
|
mod derivation_tests;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "tree_tests.rs"]
|
||||||
|
mod tree_tests;
|
||||||
|
|
||||||
use crate::{error::KernelError, generations::SlotGenerations, resource::Resource};
|
use crate::{error::KernelError, generations::SlotGenerations, resource::Resource};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
214
crates/domain/src/capability/tree.rs
Normal file
214
crates/domain/src/capability/tree.rs
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
//! The capability derivation tree with explicit links, so deleting a
|
||||||
|
//! capability costs its number of children and revoking costs the size of
|
||||||
|
//! the subtree, instead of a scan of every capability in the system.
|
||||||
|
//!
|
||||||
|
//! Nodes are numbered `0..N` (the kernel flattens process and slot). Each
|
||||||
|
//! live node knows its parent, its first child and its siblings. The
|
||||||
|
//! fixpoint in `derivation::descendants` stays as the reference this tree
|
||||||
|
//! is checked against.
|
||||||
|
|
||||||
|
/// Longest walk any operation may take: every node visited once, plus the
|
||||||
|
/// climb back up. Corrupted links can therefore never hang the kernel.
|
||||||
|
const fn budget(n: usize) -> usize {
|
||||||
|
2 * n + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
|
struct Node {
|
||||||
|
live: bool,
|
||||||
|
parent: Option<u16>,
|
||||||
|
first_child: Option<u16>,
|
||||||
|
prev: Option<u16>,
|
||||||
|
next: Option<u16>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CapTree<const N: usize> {
|
||||||
|
nodes: [Node; N],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Default for CapTree<N> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(i: usize) -> Option<u16> {
|
||||||
|
u16::try_from(i).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> CapTree<N> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
nodes: [Node::default(); N],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn node(&self, i: usize) -> Option<&Node> {
|
||||||
|
self.nodes.get(i).filter(|n| n.live)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn node_mut(&mut self, i: usize) -> Option<&mut Node> {
|
||||||
|
self.nodes.get_mut(i).filter(|n| n.live)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_live(&self, i: usize) -> bool {
|
||||||
|
self.node(i).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parent(&self, i: usize) -> Option<usize> {
|
||||||
|
self.node(i)?.parent.map(usize::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds `i` as a child of `parent` (or as a root). Fails, changing
|
||||||
|
/// nothing, if `i` is already live or out of range, or `parent` is not
|
||||||
|
/// live.
|
||||||
|
pub fn attach(&mut self, i: usize, parent: Option<usize>) -> bool {
|
||||||
|
let (Some(me), Some(node)) = (id(i), self.nodes.get(i)) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if node.live || parent.is_some_and(|p| p == i || !self.is_live(p)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let next = parent
|
||||||
|
.and_then(|p| self.node(p))
|
||||||
|
.and_then(|p| p.first_child);
|
||||||
|
if let Some(n) = next.and_then(|n| self.node_mut(n.into())) {
|
||||||
|
n.prev = Some(me);
|
||||||
|
}
|
||||||
|
if let Some(p) = parent.and_then(|p| self.node_mut(p)) {
|
||||||
|
p.first_child = Some(me);
|
||||||
|
}
|
||||||
|
if let Some(node) = self.nodes.get_mut(i) {
|
||||||
|
*node = Node {
|
||||||
|
live: true,
|
||||||
|
parent: parent.and_then(id),
|
||||||
|
first_child: None,
|
||||||
|
prev: None,
|
||||||
|
next,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unlinks `i` from its parent's child list, leaving its own children.
|
||||||
|
fn unlink(&mut self, i: usize) {
|
||||||
|
let Some(node) = self.node(i).copied() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
match node.prev {
|
||||||
|
Some(p) => {
|
||||||
|
if let Some(p) = self.node_mut(p.into()) {
|
||||||
|
p.next = node.next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
if let Some(p) = node.parent.and_then(|p| self.node_mut(p.into())) {
|
||||||
|
p.first_child = node.next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(n) = node.next.and_then(|n| self.node_mut(n.into())) {
|
||||||
|
n.prev = node.prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes `i`; its children are adopted by its parent (or become
|
||||||
|
/// roots), so no descendant escapes a revocation further up.
|
||||||
|
pub fn detach(&mut self, i: usize) {
|
||||||
|
let Some(node) = self.node(i).copied() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.unlink(i);
|
||||||
|
let mut child = node.first_child;
|
||||||
|
for _ in 0..budget(N) {
|
||||||
|
let Some(c) = child else { break };
|
||||||
|
let next = self.node(c.into()).and_then(|n| n.next);
|
||||||
|
if let Some(n) = self.node_mut(c.into()) {
|
||||||
|
n.parent = None;
|
||||||
|
n.prev = None;
|
||||||
|
n.next = None;
|
||||||
|
}
|
||||||
|
// Re-attach under the grandparent; `attach` needs a dead node.
|
||||||
|
if let Some(n) = self.nodes.get_mut(usize::from(c)) {
|
||||||
|
n.live = false;
|
||||||
|
let kids = n.first_child;
|
||||||
|
if self.attach(c.into(), node.parent.map(usize::from))
|
||||||
|
&& let Some(n) = self.nodes.get_mut(usize::from(c))
|
||||||
|
{
|
||||||
|
n.first_child = kids;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
child = next;
|
||||||
|
}
|
||||||
|
if let Some(n) = self.nodes.get_mut(i) {
|
||||||
|
*n = Node::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The direct children of `i`, written into `out`; returns how many.
|
||||||
|
pub fn children(&self, i: usize, out: &mut [u16; N]) -> usize {
|
||||||
|
let mut count = 0;
|
||||||
|
let mut cur = self.node(i).and_then(|n| n.first_child);
|
||||||
|
for _ in 0..budget(N) {
|
||||||
|
let Some(c) = cur else { break };
|
||||||
|
if let Some(slot) = out.get_mut(count) {
|
||||||
|
*slot = c;
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
cur = self.node(c.into()).and_then(|n| n.next);
|
||||||
|
}
|
||||||
|
count
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Every node strictly below `root`, written into `out` in pre-order;
|
||||||
|
/// returns how many.
|
||||||
|
pub fn descendants(&self, root: usize, out: &mut [u16; N]) -> usize {
|
||||||
|
let mut count = 0;
|
||||||
|
let Some(start) = self.node(root) else {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
let mut cur = start.first_child;
|
||||||
|
for _ in 0..budget(N) {
|
||||||
|
let Some(c) = cur else { break };
|
||||||
|
if let Some(slot) = out.get_mut(count) {
|
||||||
|
*slot = c;
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
cur = self.next_in_subtree(root, c.into());
|
||||||
|
}
|
||||||
|
count
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The node after `cur` in a pre-order walk of `root`'s subtree.
|
||||||
|
fn next_in_subtree(&self, root: usize, cur: usize) -> Option<u16> {
|
||||||
|
if let Some(child) = self.node(cur)?.first_child {
|
||||||
|
return Some(child);
|
||||||
|
}
|
||||||
|
let mut at = cur;
|
||||||
|
for _ in 0..budget(N) {
|
||||||
|
if at == root {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let node = self.node(at)?;
|
||||||
|
if let Some(next) = node.next {
|
||||||
|
return Some(next);
|
||||||
|
}
|
||||||
|
at = node.parent?.into();
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes every node strictly below `root`, keeping `root`.
|
||||||
|
pub fn remove_descendants(&mut self, root: usize) {
|
||||||
|
let mut doomed = [0u16; N];
|
||||||
|
let n = self.descendants(root, &mut doomed);
|
||||||
|
for d in doomed.iter().take(n) {
|
||||||
|
if let Some(node) = self.nodes.get_mut(usize::from(*d)) {
|
||||||
|
*node = Node::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(r) = self.node_mut(root) {
|
||||||
|
r.first_child = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
120
crates/domain/src/capability/tree_tests.rs
Normal file
120
crates/domain/src/capability/tree_tests.rs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
use super::derivation::descendants;
|
||||||
|
use super::tree::CapTree;
|
||||||
|
|
||||||
|
const N: usize = 12;
|
||||||
|
|
||||||
|
/// The tree as a plain parent array, updated by the rules the tree must
|
||||||
|
/// follow; `descendants` from derivation.rs answers subtree questions.
|
||||||
|
struct Model {
|
||||||
|
live: [bool; N],
|
||||||
|
parents: [Option<u16>; N],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Model {
|
||||||
|
fn detach(&mut self, i: usize) {
|
||||||
|
let grandparent = self.parents[i];
|
||||||
|
for p in self.parents.iter_mut() {
|
||||||
|
if *p == Some(i as u16) {
|
||||||
|
*p = grandparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.live[i] = false;
|
||||||
|
self.parents[i] = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn below(&self, root: usize) -> [bool; N] {
|
||||||
|
let mut d = descendants(&self.parents, root);
|
||||||
|
d[root] = false;
|
||||||
|
d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Rng(u64);
|
||||||
|
impl Rng {
|
||||||
|
fn below(&mut self, n: usize) -> usize {
|
||||||
|
self.0 ^= self.0 << 13;
|
||||||
|
self.0 ^= self.0 >> 7;
|
||||||
|
self.0 ^= self.0 << 17;
|
||||||
|
(self.0 % n as u64) as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check(t: &CapTree<N>, m: &Model) {
|
||||||
|
for i in 0..N {
|
||||||
|
assert_eq!(t.is_live(i), m.live[i], "liveness of {i}");
|
||||||
|
assert_eq!(t.parent(i), m.parents[i].map(usize::from), "parent of {i}");
|
||||||
|
if !m.live[i] {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut out = [0u16; N];
|
||||||
|
let n = t.descendants(i, &mut out);
|
||||||
|
let mut got = [false; N];
|
||||||
|
for d in &out[..n] {
|
||||||
|
assert!(!got[*d as usize], "{d} listed twice under {i}");
|
||||||
|
got[*d as usize] = true;
|
||||||
|
}
|
||||||
|
assert_eq!(got, m.below(i), "subtree of {i}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn matches_the_parent_array_model_under_random_operations() {
|
||||||
|
for seed in 1..200u64 {
|
||||||
|
let mut rng = Rng(seed.wrapping_mul(0x9E37_79B9_7F4A_7C15));
|
||||||
|
let mut t = CapTree::<N>::new();
|
||||||
|
let mut m = Model {
|
||||||
|
live: [false; N],
|
||||||
|
parents: [None; N],
|
||||||
|
};
|
||||||
|
for _ in 0..300 {
|
||||||
|
let i = rng.below(N);
|
||||||
|
match rng.below(4) {
|
||||||
|
0 | 1 => {
|
||||||
|
let parent = match rng.below(3) {
|
||||||
|
0 => None,
|
||||||
|
_ => Some(rng.below(N)),
|
||||||
|
};
|
||||||
|
let ok = !m.live[i] && parent.is_none_or(|p| p != i && m.live[p]);
|
||||||
|
assert_eq!(t.attach(i, parent), ok, "attach {i} under {parent:?}");
|
||||||
|
if ok {
|
||||||
|
m.live[i] = true;
|
||||||
|
m.parents[i] = parent.map(|p| p as u16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
t.detach(i);
|
||||||
|
if m.live[i] {
|
||||||
|
m.detach(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
t.remove_descendants(i);
|
||||||
|
if m.live[i] {
|
||||||
|
let below = m.below(i);
|
||||||
|
for (j, b) in below.iter().enumerate() {
|
||||||
|
if *b {
|
||||||
|
m.live[j] = false;
|
||||||
|
m.parents[j] = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check(&t, &m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn detaching_a_middle_node_hands_its_children_up() {
|
||||||
|
let mut t = CapTree::<4>::new();
|
||||||
|
assert!(t.attach(0, None));
|
||||||
|
assert!(t.attach(1, Some(0)));
|
||||||
|
assert!(t.attach(2, Some(1)));
|
||||||
|
assert!(t.attach(3, Some(1)));
|
||||||
|
t.detach(1);
|
||||||
|
assert_eq!(t.parent(2), Some(0));
|
||||||
|
assert_eq!(t.parent(3), Some(0));
|
||||||
|
let mut out = [0u16; 4];
|
||||||
|
assert_eq!(t.descendants(0, &mut out), 2);
|
||||||
|
}
|
||||||
@@ -154,13 +154,6 @@ impl EndpointTable {
|
|||||||
self.generations.advance(id.raw() as usize);
|
self.generations.advance(id.raw() as usize);
|
||||||
Some(ep)
|
Some(ep)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_thread_from_all(&mut self, thread: ThreadId) {
|
|
||||||
for ep in self.endpoints.iter_mut().flatten() {
|
|
||||||
ep.remove_sender(thread);
|
|
||||||
ep.remove_receiver(thread);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for EndpointTable {
|
impl Default for EndpointTable {
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ pub struct FrameTable<const N: usize = MAX_FRAMES> {
|
|||||||
total: usize,
|
total: usize,
|
||||||
frames: [Option<FrameUse>; N],
|
frames: [Option<FrameUse>; N],
|
||||||
free: usize,
|
free: usize,
|
||||||
|
/// Where the next allocation starts looking: just past the last one.
|
||||||
|
cursor: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> FrameTable<N> {
|
impl<const N: usize> FrameTable<N> {
|
||||||
@@ -60,6 +62,7 @@ impl<const N: usize> FrameTable<N> {
|
|||||||
total,
|
total,
|
||||||
frames: [None; N],
|
frames: [None; N],
|
||||||
free: total,
|
free: total,
|
||||||
|
cursor: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,14 +104,21 @@ impl<const N: usize> FrameTable<N> {
|
|||||||
self.frames.get(self.index_of(pa)?)?.as_ref()
|
self.frames.get(self.index_of(pa)?)?.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Next-fit: searches from just past the previous allocation and wraps
|
||||||
|
/// once, so a run of allocations costs O(1) each instead of rescanning
|
||||||
|
/// the allocated prefix every time.
|
||||||
pub fn alloc(&mut self, usage: FrameUse) -> Option<PhysicalAddress> {
|
pub fn alloc(&mut self, usage: FrameUse) -> Option<PhysicalAddress> {
|
||||||
let (idx, slot) = self
|
if self.free == 0 {
|
||||||
.live_mut()
|
return None;
|
||||||
.iter_mut()
|
}
|
||||||
.enumerate()
|
let total = self.total;
|
||||||
.find(|(_, f)| f.is_none())?;
|
let start = self.cursor.min(total);
|
||||||
*slot = Some(usage);
|
let idx = (start..total)
|
||||||
|
.chain(0..start)
|
||||||
|
.find(|&i| self.frames.get(i).is_some_and(Option::is_none))?;
|
||||||
|
*self.frames.get_mut(idx)? = Some(usage);
|
||||||
self.free -= 1;
|
self.free -= 1;
|
||||||
|
self.cursor = idx + 1;
|
||||||
Some(self.address_of(idx))
|
Some(self.address_of(idx))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,6 +178,29 @@ impl<const N: usize> FrameTable<N> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The frame mapped at `va` in `owner`, if `owner` holds it as a user page.
|
/// The frame mapped at `va` in `owner`, if `owner` holds it as a user page.
|
||||||
|
/// The perms of the frame at `pa` if it is `owner`'s user page at
|
||||||
|
/// `va`; `None` for any other frame. O(1): the caller supplies the
|
||||||
|
/// frame, this only confirms it.
|
||||||
|
pub fn user_at(
|
||||||
|
&self,
|
||||||
|
pa: PhysicalAddress,
|
||||||
|
owner: ProcessId,
|
||||||
|
va: VirtualAddress,
|
||||||
|
) -> Option<PagePerms> {
|
||||||
|
let page = u32::try_from(va.raw() / PAGE_SIZE).ok()?;
|
||||||
|
if !va.raw().is_multiple_of(PAGE_SIZE) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
match self.get(pa)? {
|
||||||
|
FrameUse::User {
|
||||||
|
owner: o,
|
||||||
|
page: p,
|
||||||
|
perms,
|
||||||
|
} if *o == owner && *p == page => Some(*perms),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn find_user(
|
pub fn find_user(
|
||||||
&self,
|
&self,
|
||||||
owner: ProcessId,
|
owner: ProcessId,
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ fn any_table() -> FrameTable<N> {
|
|||||||
total: N,
|
total: N,
|
||||||
frames,
|
frames,
|
||||||
free,
|
free,
|
||||||
|
// Next-fit may resume anywhere, so the proofs start it anywhere.
|
||||||
|
cursor: kani::any::<usize>() % (N + 1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,11 @@ impl Notification {
|
|||||||
self.unbind(thread);
|
self.unbind(thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Takes `thread` out of the waiters, leaving any binding.
|
||||||
|
pub fn remove_waiter(&mut self, thread: ThreadId) {
|
||||||
|
self.waiters.remove_thread(thread);
|
||||||
|
}
|
||||||
|
|
||||||
/// Drains every waiter, e.g. when the notification is destroyed.
|
/// Drains every waiter, e.g. when the notification is destroyed.
|
||||||
pub fn pop_waiter(&mut self) -> Option<ThreadId> {
|
pub fn pop_waiter(&mut self) -> Option<ThreadId> {
|
||||||
self.waiters.pop()
|
self.waiters.pop()
|
||||||
@@ -177,12 +182,6 @@ impl NotificationTable {
|
|||||||
.filter(move |n| n.owner == owner)
|
.filter(move |n| n.owner == owner)
|
||||||
.map(|n| n.id)
|
.map(|n| n.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_thread_from_all(&mut self, thread: ThreadId) {
|
|
||||||
for n in self.slots.iter_mut().flatten() {
|
|
||||||
n.remove_thread(thread);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for NotificationTable {
|
impl Default for NotificationTable {
|
||||||
|
|||||||
@@ -2,9 +2,15 @@
|
|||||||
//! Delegate, IPC transfer) records its parent; `Revoke` deletes a subtree
|
//! Delegate, IPC transfer) records its parent; `Revoke` deletes a subtree
|
||||||
//! and deleting a capability re-parents its children, so the tree can only
|
//! and deleting a capability re-parents its children, so the tree can only
|
||||||
//! shrink by explicit deletion, never by a middleman stepping out.
|
//! shrink by explicit deletion, never by a middleman stepping out.
|
||||||
|
//!
|
||||||
|
//! Each capability records its parent (`Capability::parent`), and
|
||||||
|
//! `Kernel::tree` holds the same relation as explicit links, so deletion
|
||||||
|
//! and revocation cost the subtree, not a scan of every capability. Every
|
||||||
|
//! insertion and removal of a capability goes through this module, which
|
||||||
|
//! keeps the two in step.
|
||||||
|
|
||||||
use domain::{
|
use domain::{
|
||||||
capability::{CapRef, Capability, MAX_CAPS_PER_PROCESS, derivation::descendants},
|
capability::{CapRef, Capability, MAX_CAPS_PER_PROCESS},
|
||||||
error::KernelError,
|
error::KernelError,
|
||||||
process::MAX_PROCESSES,
|
process::MAX_PROCESSES,
|
||||||
};
|
};
|
||||||
@@ -17,7 +23,7 @@ use types::{
|
|||||||
use crate::Kernel;
|
use crate::Kernel;
|
||||||
|
|
||||||
/// Every capability slot in the system, flattened.
|
/// Every capability slot in the system, flattened.
|
||||||
const ALL_CAPS: usize = MAX_PROCESSES * MAX_CAPS_PER_PROCESS;
|
pub(crate) const ALL_CAPS: usize = MAX_PROCESSES * MAX_CAPS_PER_PROCESS;
|
||||||
|
|
||||||
fn flat(pid: ProcessId, slot: CapSlot) -> usize {
|
fn flat(pid: ProcessId, slot: CapSlot) -> usize {
|
||||||
pid.raw() as usize * MAX_CAPS_PER_PROCESS + slot.value() as usize
|
pid.raw() as usize * MAX_CAPS_PER_PROCESS + slot.value() as usize
|
||||||
@@ -46,47 +52,55 @@ impl<P: Platform> Kernel<P> {
|
|||||||
self.cap_ref(r.process, r.slot) == Some(r)
|
self.cap_ref(r.process, r.slot) == Some(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stores `cap` in `to`'s table as a child of `parent`.
|
/// Stores `cap` in `to`'s table as a child of `parent` (a root if
|
||||||
|
/// `None`).
|
||||||
pub(crate) fn insert_derived(
|
pub(crate) fn insert_derived(
|
||||||
&mut self,
|
&mut self,
|
||||||
to: ProcessId,
|
to: ProcessId,
|
||||||
cap: Capability,
|
cap: Capability,
|
||||||
parent: Option<CapRef>,
|
parent: Option<CapRef>,
|
||||||
) -> Result<CapSlot, KernelError> {
|
) -> Result<CapSlot, KernelError> {
|
||||||
self.processes
|
let parent = parent.filter(|r| self.resolves(*r));
|
||||||
|
let slot = self
|
||||||
|
.processes
|
||||||
.get_mut(to)?
|
.get_mut(to)?
|
||||||
.capabilities_mut()
|
.capabilities_mut()
|
||||||
.insert(cap.with_parent(parent))
|
.insert(cap.with_parent(parent))?;
|
||||||
}
|
let linked = self
|
||||||
|
.tree
|
||||||
/// Calls `f` with every live capability's flat index and reference.
|
.attach(flat(to, slot), parent.map(|r| flat(r.process, r.slot)));
|
||||||
fn for_each_cap(&self, mut f: impl FnMut(usize, CapRef, &Capability)) {
|
if !linked {
|
||||||
for p in 0..MAX_PROCESSES {
|
// A slot the tree still thinks is in use: refuse rather than
|
||||||
let pid = ProcessId::from_raw(p as u16);
|
// let the two disagree.
|
||||||
let Ok(process) = self.processes.get(pid) else {
|
let _ = self
|
||||||
continue;
|
.processes
|
||||||
};
|
.get_mut(to)
|
||||||
for (slot, cap) in process.capabilities().iter() {
|
.map(|p| p.capabilities_mut().remove(slot));
|
||||||
if let Some(r) = self.cap_ref(pid, slot) {
|
return Err(KernelError::NoFreeSlot);
|
||||||
f(flat(pid, slot), r, cap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Ok(slot)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes every region mapping made through `via`.
|
/// Removes every region mapping made through `via`.
|
||||||
fn unmap_mappings_via(&mut self, via: CapRef) {
|
fn unmap_mappings_via(&mut self, via: CapRef) {
|
||||||
let mut doomed: [Option<RegionId>; domain::memory::MAX_REGIONS] =
|
self.unmap_mappings_where(|r| r == via);
|
||||||
[None; domain::memory::MAX_REGIONS];
|
}
|
||||||
for (slot, (id, _)) in doomed.iter_mut().zip(
|
|
||||||
self.regions
|
/// Removes every region mapping made through a capability `doomed`
|
||||||
.iter()
|
/// picks, in one pass over the regions.
|
||||||
.filter(|(_, r)| r.mappings().any(|m| m.via == Some(via))),
|
fn unmap_mappings_where(&mut self, doomed: impl Fn(CapRef) -> bool) {
|
||||||
) {
|
const MAX: usize = domain::memory::MAX_REGIONS * domain::memory::MAX_REGION_MAPPINGS;
|
||||||
*slot = Some(id);
|
let mut found: [Option<(RegionId, ProcessId)>; MAX] = [None; MAX];
|
||||||
|
let hits = self.regions.iter().flat_map(|(id, r)| {
|
||||||
|
r.mappings()
|
||||||
|
.filter(|m| m.via.is_some_and(&doomed))
|
||||||
|
.map(move |m| (id, m.process))
|
||||||
|
});
|
||||||
|
for (slot, hit) in found.iter_mut().zip(hits) {
|
||||||
|
*slot = Some(hit);
|
||||||
}
|
}
|
||||||
for id in doomed.into_iter().flatten() {
|
for (id, pid) in found.into_iter().flatten() {
|
||||||
self.unmap_region_from(id, via.process);
|
self.unmap_region_from(id, pid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,22 +113,17 @@ impl<P: Platform> Kernel<P> {
|
|||||||
let grandparent = self.processes.get(pid)?.capabilities().get(slot)?.parent();
|
let grandparent = self.processes.get(pid)?.capabilities().get(slot)?.parent();
|
||||||
self.unmap_mappings_via(me);
|
self.unmap_mappings_via(me);
|
||||||
|
|
||||||
let mut children = [false; ALL_CAPS];
|
let mut children = [0u16; ALL_CAPS];
|
||||||
self.for_each_cap(|i, _, cap| {
|
let n = self.tree.children(flat(pid, slot), &mut children);
|
||||||
if cap.parent() == Some(me)
|
for c in children.iter().take(n) {
|
||||||
&& let Some(c) = children.get_mut(i)
|
if let Some((cpid, cslot)) = unflat(usize::from(*c))
|
||||||
{
|
|
||||||
*c = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
for (i, _) in children.iter().enumerate().filter(|(_, c)| **c) {
|
|
||||||
if let Some((cpid, cslot)) = unflat(i)
|
|
||||||
&& let Ok(p) = self.processes.get_mut(cpid)
|
&& let Ok(p) = self.processes.get_mut(cpid)
|
||||||
&& let Ok(child) = p.capabilities_mut().get_mut(cslot)
|
&& let Ok(child) = p.capabilities_mut().get_mut(cslot)
|
||||||
{
|
{
|
||||||
child.set_parent(grandparent);
|
child.set_parent(grandparent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.tree.detach(flat(pid, slot));
|
||||||
self.processes.get_mut(pid)?.capabilities_mut().remove(slot)
|
self.processes.get_mut(pid)?.capabilities_mut().remove(slot)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,31 +137,30 @@ impl<P: Platform> Kernel<P> {
|
|||||||
if self.cap_ref(pid, slot).is_none() {
|
if self.cap_ref(pid, slot).is_none() {
|
||||||
return Err(KernelError::InvalidSlot(slot));
|
return Err(KernelError::InvalidSlot(slot));
|
||||||
}
|
}
|
||||||
// Snapshot the tree as parent indices. A parent that no longer
|
|
||||||
// resolves cannot exist (deletion re-parents), but is treated as
|
|
||||||
// a root rather than trusted.
|
|
||||||
let mut parents: [Option<u16>; ALL_CAPS] = [None; ALL_CAPS];
|
|
||||||
self.for_each_cap(|i, _, cap| {
|
|
||||||
if let Some(p) = parents.get_mut(i) {
|
|
||||||
*p = cap
|
|
||||||
.parent()
|
|
||||||
.filter(|r| self.resolves(*r))
|
|
||||||
.map(|r| flat(r.process, r.slot) as u16);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let root = flat(pid, slot);
|
let root = flat(pid, slot);
|
||||||
let marked = descendants(&parents, root);
|
let mut doomed = [0u16; ALL_CAPS];
|
||||||
for (i, _) in marked.iter().enumerate().filter(|(i, m)| **m && *i != root) {
|
let n = self.tree.descendants(root, &mut doomed);
|
||||||
if let Some((dpid, dslot)) = unflat(i)
|
let mut marked = [false; ALL_CAPS];
|
||||||
&& let Some(r) = self.cap_ref(dpid, dslot)
|
for d in doomed.iter().take(n) {
|
||||||
{
|
if let Some(m) = marked.get_mut(usize::from(*d)) {
|
||||||
self.unmap_mappings_via(r);
|
*m = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.unmap_mappings_where(|r| {
|
||||||
|
marked
|
||||||
|
.get(flat(r.process, r.slot))
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(false)
|
||||||
|
});
|
||||||
|
for d in doomed.iter().take(n) {
|
||||||
|
if let Some((dpid, dslot)) = unflat(usize::from(*d)) {
|
||||||
let _ = self
|
let _ = self
|
||||||
.processes
|
.processes
|
||||||
.get_mut(dpid)
|
.get_mut(dpid)
|
||||||
.map(|p| p.capabilities_mut().remove(dslot));
|
.map(|p| p.capabilities_mut().remove(dslot));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.tree.remove_descendants(root);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,10 @@ pub struct Kernel<P: Platform> {
|
|||||||
pending_results: [Option<SyscallResult>; MAX_THREADS],
|
pending_results: [Option<SyscallResult>; MAX_THREADS],
|
||||||
/// The core each thread runs on (see smp.rs).
|
/// The core each thread runs on (see smp.rs).
|
||||||
homes: [types::id::CoreId; MAX_THREADS],
|
homes: [types::id::CoreId; MAX_THREADS],
|
||||||
|
/// The capability derivation tree as links (see captree.rs).
|
||||||
|
tree: domain::capability::tree::CapTree<{ captree::ALL_CAPS }>,
|
||||||
|
/// Where each process's next heap search starts (see memory.rs).
|
||||||
|
heap_cursors: [usize; MAX_PROCESSES],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Platform> Kernel<P> {
|
impl<P: Platform> Kernel<P> {
|
||||||
@@ -87,6 +91,8 @@ impl<P: Platform> Kernel<P> {
|
|||||||
ipc: [syscall::IpcState::default(); MAX_THREADS],
|
ipc: [syscall::IpcState::default(); MAX_THREADS],
|
||||||
pending_results: [const { None }; MAX_THREADS],
|
pending_results: [const { None }; MAX_THREADS],
|
||||||
homes: [types::id::CoreId::from_raw(0); MAX_THREADS],
|
homes: [types::id::CoreId::from_raw(0); MAX_THREADS],
|
||||||
|
tree: domain::capability::tree::CapTree::new(),
|
||||||
|
heap_cursors: [0; MAX_PROCESSES],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,6 +126,8 @@ impl<P: Platform> Kernel<P> {
|
|||||||
addr_of_mut!((*slot).ipc).write([syscall::IpcState::default(); MAX_THREADS]);
|
addr_of_mut!((*slot).ipc).write([syscall::IpcState::default(); MAX_THREADS]);
|
||||||
addr_of_mut!((*slot).pending_results).write([const { None }; MAX_THREADS]);
|
addr_of_mut!((*slot).pending_results).write([const { None }; MAX_THREADS]);
|
||||||
addr_of_mut!((*slot).homes).write([types::id::CoreId::from_raw(0); MAX_THREADS]);
|
addr_of_mut!((*slot).homes).write([types::id::CoreId::from_raw(0); MAX_THREADS]);
|
||||||
|
addr_of_mut!((*slot).tree).write(domain::capability::tree::CapTree::new());
|
||||||
|
addr_of_mut!((*slot).heap_cursors).write([0; MAX_PROCESSES]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,10 +149,7 @@ impl<P: Platform> Kernel<P> {
|
|||||||
PermissionSet::empty().with(Permission::Execute),
|
PermissionSet::empty().with(Permission::Execute),
|
||||||
Generation::initial(),
|
Generation::initial(),
|
||||||
);
|
);
|
||||||
self.processes
|
self.insert_derived(pid, cap, None)?;
|
||||||
.get_mut(pid)?
|
|
||||||
.capabilities_mut()
|
|
||||||
.insert(cap)?;
|
|
||||||
|
|
||||||
let core = self.platform.core_id();
|
let core = self.platform.core_id();
|
||||||
self.set_home(tid, core);
|
self.set_home(tid, core);
|
||||||
@@ -285,8 +290,14 @@ 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 tid in thread_ids.iter().flatten() {
|
for tid in thread_ids.iter().flatten() {
|
||||||
self.endpoints.remove_thread_from_all(*tid);
|
self.leave_queue(*tid);
|
||||||
self.notifications.remove_thread_from_all(*tid);
|
if let Some(n) = self
|
||||||
|
.ipc(*tid)
|
||||||
|
.and_then(|s| s.bound)
|
||||||
|
.and_then(|n| self.notifications.get_mut(n))
|
||||||
|
{
|
||||||
|
n.remove_thread(*tid);
|
||||||
|
}
|
||||||
let ipc = self.ipc_mut(*tid).map(core::mem::take).unwrap_or_default();
|
let ipc = self.ipc_mut(*tid).map(core::mem::take).unwrap_or_default();
|
||||||
if let Some(caller) = ipc.reply_to {
|
if let Some(caller) = ipc.reply_to {
|
||||||
self.release_caller(caller);
|
self.release_caller(caller);
|
||||||
@@ -298,6 +309,29 @@ impl<P: Platform> Kernel<P> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Takes a blocked thread off the one queue its block reason names, so
|
||||||
|
/// nothing needs to scan every endpoint and notification.
|
||||||
|
pub(crate) fn leave_queue(&mut self, tid: ThreadId) {
|
||||||
|
use domain::thread::BlockReason::*;
|
||||||
|
let Ok(ThreadState::Blocked(reason)) = self.threads.get(tid).map(|t| *t.state()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
match reason {
|
||||||
|
Sending(ep) | Calling(ep) => {
|
||||||
|
self.endpoints.get_mut(ep).map(|e| e.remove_sender(tid));
|
||||||
|
}
|
||||||
|
Receiving(ep) => {
|
||||||
|
self.endpoints.get_mut(ep).map(|e| e.remove_receiver(tid));
|
||||||
|
}
|
||||||
|
WaitingNotification(n) => {
|
||||||
|
if let Some(n) = self.notifications.get_mut(n) {
|
||||||
|
n.remove_waiter(tid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AwaitingReply => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Destroys every endpoint `owner` created, waking anyone blocked on
|
/// Destroys every endpoint `owner` created, waking anyone blocked on
|
||||||
/// them with an error instead of leaving them blocked forever.
|
/// them with an error instead of leaving them blocked forever.
|
||||||
pub(crate) fn destroy_endpoints_owned_by(&mut self, owner: ProcessId) {
|
pub(crate) fn destroy_endpoints_owned_by(&mut self, owner: ProcessId) {
|
||||||
@@ -387,6 +421,6 @@ impl<P: Platform> Kernel<P> {
|
|||||||
.resource_generation(&resource)
|
.resource_generation(&resource)
|
||||||
.ok_or(KernelError::NoSuchObject)?;
|
.ok_or(KernelError::NoSuchObject)?;
|
||||||
let cap = Capability::new(resource, permissions, generation);
|
let cap = Capability::new(resource, permissions, generation);
|
||||||
self.processes.get_mut(to)?.capabilities_mut().insert(cap)
|
self.insert_derived(to, cap, None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,8 +70,19 @@ impl<P: Platform> Kernel<P> {
|
|||||||
&& end <= P::USER_VA_END
|
&& end <= P::USER_VA_END
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `pid`'s user page at `va`: the platform's page table says where it
|
||||||
|
/// points, the frame table confirms the frame really is that page.
|
||||||
|
fn user_frame(
|
||||||
|
&self,
|
||||||
|
pid: ProcessId,
|
||||||
|
va: VirtualAddress,
|
||||||
|
) -> Option<(PhysicalAddress, PagePerms)> {
|
||||||
|
let pa = self.platform.translate(pid, va)?;
|
||||||
|
self.frames.user_at(pa, pid, va).map(|perms| (pa, perms))
|
||||||
|
}
|
||||||
|
|
||||||
fn is_mapped(&self, pid: ProcessId, va: VirtualAddress) -> bool {
|
fn is_mapped(&self, pid: ProcessId, va: VirtualAddress) -> bool {
|
||||||
self.frames.find_user(pid, va).is_some() || self.regions.covering(pid, va).is_some()
|
self.user_frame(pid, va).is_some() || self.regions.covering(pid, va).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn range_is_free(&self, pid: ProcessId, va: VirtualAddress, pages: usize) -> bool {
|
fn range_is_free(&self, pid: ProcessId, va: VirtualAddress, pages: usize) -> bool {
|
||||||
@@ -173,7 +184,7 @@ impl<P: Platform> Kernel<P> {
|
|||||||
|
|
||||||
/// Removes one user page of `pid` and returns its frame to the pool.
|
/// Removes one user page of `pid` and returns its frame to the pool.
|
||||||
fn unmap_and_free(&mut self, pid: ProcessId, va: VirtualAddress) {
|
fn unmap_and_free(&mut self, pid: ProcessId, va: VirtualAddress) {
|
||||||
if let Some((pa, _)) = self.frames.find_user(pid, va) {
|
if let Some((pa, _)) = self.user_frame(pid, va) {
|
||||||
self.platform.unmap_page(pid, va);
|
self.platform.unmap_page(pid, va);
|
||||||
self.frames.free(pa);
|
self.frames.free(pa);
|
||||||
if let Some(q) = self.quotas.get_mut(pid.raw() as usize) {
|
if let Some(q) = self.quotas.get_mut(pid.raw() as usize) {
|
||||||
@@ -185,7 +196,7 @@ impl<P: Platform> Kernel<P> {
|
|||||||
/// Frame backing the user page at `va` in `pid`. Lets boot code fill
|
/// Frame backing the user page at `va` in `pid`. Lets boot code fill
|
||||||
/// pages it has just mapped for a process.
|
/// pages it has just mapped for a process.
|
||||||
pub fn user_page_frame(&self, pid: ProcessId, va: VirtualAddress) -> Option<PhysicalAddress> {
|
pub fn user_page_frame(&self, pid: ProcessId, va: VirtualAddress) -> Option<PhysicalAddress> {
|
||||||
self.frames.find_user(pid, va).map(|(pa, _)| pa)
|
self.user_frame(pid, va).map(|(pa, _)| pa)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maps `pages` fresh zeroed pages at `va`, all or nothing.
|
/// Maps `pages` fresh zeroed pages at `va`, all or nothing.
|
||||||
@@ -213,13 +224,35 @@ impl<P: Platform> Kernel<P> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// First-fit search for `pages` free pages in the heap part of the window.
|
/// Next-fit search for `pages` free pages in the heap part of the
|
||||||
|
/// window: from just past `pid`'s previous request, then once more from
|
||||||
|
/// the bottom. Requests in a row cost O(pages) each instead of
|
||||||
|
/// rescanning everything allocated before.
|
||||||
pub(crate) fn find_free_heap_range(
|
pub(crate) fn find_free_heap_range(
|
||||||
&self,
|
&self,
|
||||||
pid: ProcessId,
|
pid: ProcessId,
|
||||||
pages: usize,
|
pages: usize,
|
||||||
) -> Option<VirtualAddress> {
|
) -> Option<VirtualAddress> {
|
||||||
let mut candidate = USER_HEAP_START.max(P::USER_VA_START);
|
let bottom = USER_HEAP_START.max(P::USER_VA_START);
|
||||||
|
let cursor = self
|
||||||
|
.heap_cursors
|
||||||
|
.get(pid.raw() as usize)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(0)
|
||||||
|
.max(bottom);
|
||||||
|
self.first_fit_from(pid, cursor, pages)
|
||||||
|
.or_else(|| self.first_fit_from(pid, bottom, pages))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Records where `pid`'s next heap search starts.
|
||||||
|
pub(crate) fn advance_heap_cursor(&mut self, pid: ProcessId, past: VirtualAddress) {
|
||||||
|
if let Some(c) = self.heap_cursors.get_mut(pid.raw() as usize) {
|
||||||
|
*c = past.raw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn first_fit_from(&self, pid: ProcessId, from: usize, pages: usize) -> Option<VirtualAddress> {
|
||||||
|
let mut candidate = from;
|
||||||
while Self::in_user_window(VirtualAddress::from_raw(candidate), pages) {
|
while Self::in_user_window(VirtualAddress::from_raw(candidate), pages) {
|
||||||
let va = VirtualAddress::from_raw(candidate);
|
let va = VirtualAddress::from_raw(candidate);
|
||||||
match (0..pages).find(|&i| self.is_mapped(pid, page_at(va, i))) {
|
match (0..pages).find(|&i| self.is_mapped(pid, page_at(va, i))) {
|
||||||
@@ -241,7 +274,7 @@ impl<P: Platform> Kernel<P> {
|
|||||||
if !Self::in_user_window(va, pages) {
|
if !Self::in_user_window(va, pages) {
|
||||||
return Err(KernelError::InvalidAddress);
|
return Err(KernelError::InvalidAddress);
|
||||||
}
|
}
|
||||||
if !(0..pages).all(|i| self.frames.find_user(pid, page_at(va, i)).is_some()) {
|
if !(0..pages).all(|i| self.user_frame(pid, page_at(va, i)).is_some()) {
|
||||||
return Err(KernelError::InvalidAddress);
|
return Err(KernelError::InvalidAddress);
|
||||||
}
|
}
|
||||||
for i in 0..pages {
|
for i in 0..pages {
|
||||||
@@ -264,8 +297,7 @@ impl<P: Platform> Kernel<P> {
|
|||||||
return Err(KernelError::InvalidAddress);
|
return Err(KernelError::InvalidAddress);
|
||||||
}
|
}
|
||||||
let (pa, _) = self
|
let (pa, _) = self
|
||||||
.frames
|
.user_frame(from, src)
|
||||||
.find_user(from, src)
|
|
||||||
.ok_or(KernelError::InvalidAddress)?;
|
.ok_or(KernelError::InvalidAddress)?;
|
||||||
self.processes.get(to)?;
|
self.processes.get(to)?;
|
||||||
if !self.range_is_free(to, dst, 1) {
|
if !self.range_is_free(to, dst, 1) {
|
||||||
@@ -478,6 +510,9 @@ impl<P: Platform> Kernel<P> {
|
|||||||
if let Some(q) = self.quotas.get_mut(pid.raw() as usize) {
|
if let Some(q) = self.quotas.get_mut(pid.raw() as usize) {
|
||||||
*q = Quota::default();
|
*q = Quota::default();
|
||||||
}
|
}
|
||||||
|
if let Some(c) = self.heap_cursors.get_mut(pid.raw() as usize) {
|
||||||
|
*c = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ impl Platform for KaniPlatform {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn unmap_page(&mut self, _process: ProcessId, _va: VirtualAddress) {}
|
fn unmap_page(&mut self, _process: ProcessId, _va: VirtualAddress) {}
|
||||||
|
fn translate(&self, _process: ProcessId, _va: VirtualAddress) -> Option<PhysicalAddress> {
|
||||||
|
None
|
||||||
|
}
|
||||||
fn zero_frame(&mut self, _pa: PhysicalAddress) {}
|
fn zero_frame(&mut self, _pa: PhysicalAddress) {}
|
||||||
fn enable_user_mode(&mut self, _entry: EntryPoint, _stack: StackPointer) -> ! {
|
fn enable_user_mode(&mut self, _entry: EntryPoint, _stack: StackPointer) -> ! {
|
||||||
kani::assume(false);
|
kani::assume(false);
|
||||||
|
|||||||
@@ -35,17 +35,17 @@ impl<P: Platform> Kernel<P> {
|
|||||||
|
|
||||||
/// Homes a new thread on the core with the fewest live threads.
|
/// Homes a new thread on the core with the fewest live threads.
|
||||||
pub(crate) fn place(&mut self, tid: ThreadId) {
|
pub(crate) fn place(&mut self, tid: ThreadId) {
|
||||||
let cores = self.scheduler.num_cores().max(1);
|
let cores = usize::from(self.scheduler.num_cores().max(1));
|
||||||
let load = |core: CoreId| {
|
let mut load = [0usize; u8::MAX as usize + 1];
|
||||||
self.threads
|
for (t, _) in self.threads.iter().filter(|(t, _)| *t != tid) {
|
||||||
.iter()
|
if let Some(n) = load.get_mut(usize::from(self.home(t).raw())) {
|
||||||
.filter(|(t, _)| *t != tid && self.home(*t) == core)
|
*n += 1;
|
||||||
.count()
|
}
|
||||||
};
|
}
|
||||||
let best = (0..cores)
|
let best = (0..cores)
|
||||||
.map(CoreId::from_raw)
|
.min_by_key(|c| load.get(*c).copied().unwrap_or(usize::MAX))
|
||||||
.min_by_key(|c| load(*c))
|
.and_then(|c| u8::try_from(c).ok())
|
||||||
.unwrap_or(CoreId::from_raw(0));
|
.map_or(CoreId::from_raw(0), CoreId::from_raw);
|
||||||
self.set_home(tid, best);
|
self.set_home(tid, best);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -449,7 +449,7 @@ impl<P: Platform> Kernel<P> {
|
|||||||
self.wake(t, SyscallResult::Received(Delivery::signal(bits)));
|
self.wake(t, SyscallResult::Received(Delivery::signal(bits)));
|
||||||
}
|
}
|
||||||
SignalEffect::WakeBound(t, bits) => {
|
SignalEffect::WakeBound(t, bits) => {
|
||||||
self.endpoints.remove_thread_from_all(t);
|
self.leave_queue(t);
|
||||||
self.wake(t, SyscallResult::Received(Delivery::signal(bits)));
|
self.wake(t, SyscallResult::Received(Delivery::signal(bits)));
|
||||||
}
|
}
|
||||||
SignalEffect::Stored => {}
|
SignalEffect::Stored => {}
|
||||||
|
|||||||
@@ -27,8 +27,14 @@ impl<P: Platform> Kernel<P> {
|
|||||||
self.find_free_heap_range(pid, pages)
|
self.find_free_heap_range(pid, pages)
|
||||||
.ok_or(KernelError::OutOfMemory)
|
.ok_or(KernelError::OutOfMemory)
|
||||||
.and_then(|va| {
|
.and_then(|va| {
|
||||||
self.map_fresh_range(pid, va, pages, PagePerms::ReadWrite)
|
self.map_fresh_range(pid, va, pages, PagePerms::ReadWrite)?;
|
||||||
.map(|()| SyscallResult::Value(va.raw()))
|
self.advance_heap_cursor(
|
||||||
|
pid,
|
||||||
|
VirtualAddress::from_raw(
|
||||||
|
va.raw() + pages * domain::memory::frame::PAGE_SIZE,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Ok(SyscallResult::Value(va.raw()))
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
183
crates/kernel/src/tests/bench.rs
Normal file
183
crates/kernel/src/tests/bench.rs
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
//! Worst-case timings of kernel paths that scan tables. Not run by default:
|
||||||
|
//! `cargo test -p kernel --lib bench -- --ignored --nocapture --test-threads 1`.
|
||||||
|
//! Numbers are host nanoseconds at the test profile's opt-level, good for
|
||||||
|
//! comparing before and after a change, not for absolute claims.
|
||||||
|
|
||||||
|
extern crate std;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::{SyscallOutcome, SyscallResult};
|
||||||
|
use domain::{capability::Capability, message::Message, resource::Resource, syscall::Syscall};
|
||||||
|
use std::time::Instant;
|
||||||
|
use types::{
|
||||||
|
bounded::{CapSlot, PageCount},
|
||||||
|
generation::Generation,
|
||||||
|
permission::{Permission, PermissionSet},
|
||||||
|
};
|
||||||
|
|
||||||
|
fn all() -> PermissionSet {
|
||||||
|
PermissionSet::empty()
|
||||||
|
.with(Permission::Read)
|
||||||
|
.with(Permission::Write)
|
||||||
|
.with(Permission::Execute)
|
||||||
|
.with(Permission::Delegate)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn report(name: &str, ops: usize, start: Instant) {
|
||||||
|
let ns = start.elapsed().as_nanos() as f64 / ops as f64;
|
||||||
|
std::eprintln!("BENCH {name}: {ns:.0} ns/op ({ops} ops)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `n` processes besides init, each with a full capability table derived
|
||||||
|
/// from one cap in init, so deleting or revoking walks the whole space.
|
||||||
|
fn crowded(
|
||||||
|
n: usize,
|
||||||
|
) -> (
|
||||||
|
Kernel<MockPlatform>,
|
||||||
|
ProcessId,
|
||||||
|
CapSlot,
|
||||||
|
std::vec::Vec<ProcessId>,
|
||||||
|
) {
|
||||||
|
let (mut k, init) = kernel_with_process();
|
||||||
|
let ep = k.create_endpoint(init).unwrap();
|
||||||
|
let root = k.grant_cap_for_test(init, Resource::Endpoint(ep), all());
|
||||||
|
let parent = k.cap_ref(init, root);
|
||||||
|
let mut pids = std::vec::Vec::new();
|
||||||
|
for _ in 0..n {
|
||||||
|
let Some(pid) = k.create_process_internal() else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
while k
|
||||||
|
.insert_derived(
|
||||||
|
pid,
|
||||||
|
Capability::new(Resource::Endpoint(ep), all(), Generation::initial()),
|
||||||
|
parent,
|
||||||
|
)
|
||||||
|
.is_ok()
|
||||||
|
{}
|
||||||
|
pids.push(pid);
|
||||||
|
}
|
||||||
|
(k, init, root, pids)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn bench_destroy_crowded_processes() {
|
||||||
|
let (mut k, _, _, pids) = crowded(60);
|
||||||
|
let start = Instant::now();
|
||||||
|
for pid in &pids {
|
||||||
|
k.destroy_process(*pid);
|
||||||
|
}
|
||||||
|
report("destroy process with 64 derived caps", pids.len(), start);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn bench_revoke_whole_space() {
|
||||||
|
let mut total = 0;
|
||||||
|
let mut runs = 0;
|
||||||
|
for _ in 0..5 {
|
||||||
|
let (mut k, init, root, pids) = crowded(60);
|
||||||
|
let start = Instant::now();
|
||||||
|
k.revoke_subtree(init, root).unwrap();
|
||||||
|
total += start.elapsed().as_nanos();
|
||||||
|
runs += 1;
|
||||||
|
assert!(
|
||||||
|
pids.iter()
|
||||||
|
.all(|p| k.cap_slots_for_test(*p).iter().all(|s| !s))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
std::eprintln!(
|
||||||
|
"BENCH revoke ~3800 caps across 60 processes: {:.0} ns/op ({runs} ops)",
|
||||||
|
total as f64 / runs as f64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn bench_drop_cap_in_crowded_space() {
|
||||||
|
let (mut k, _, _, pids) = crowded(60);
|
||||||
|
let victim = pids[0];
|
||||||
|
let start = Instant::now();
|
||||||
|
let mut ops = 0;
|
||||||
|
for slot in 0..=CapSlot::MAX {
|
||||||
|
if k.delete_cap(victim, CapSlot::new(slot).unwrap()).is_ok() {
|
||||||
|
ops += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
report("delete one cap among ~3800", ops, start);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn bench_request_pages_until_full() {
|
||||||
|
let (mut k, _) = kernel_with_process();
|
||||||
|
let start = Instant::now();
|
||||||
|
let mut ops = 0;
|
||||||
|
while let SyscallOutcome::Complete(SyscallResult::Value(_)) =
|
||||||
|
k.handle_syscall(Syscall::RequestPages {
|
||||||
|
count: PageCount::new(1).unwrap(),
|
||||||
|
})
|
||||||
|
{
|
||||||
|
ops += 1;
|
||||||
|
}
|
||||||
|
report(
|
||||||
|
"RequestPages(1) until the pool or quota runs out",
|
||||||
|
ops,
|
||||||
|
start,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn bench_ipc_round_trip() {
|
||||||
|
let (mut k, init) = kernel_with_process();
|
||||||
|
let client = k.current_thread().unwrap();
|
||||||
|
let server = k.spawn_thread_for_test(init, Priority::DEFAULT, TimeSlice::DEFAULT);
|
||||||
|
let ep = k.create_endpoint(init).unwrap();
|
||||||
|
let slot = k.grant_cap_for_test(init, Resource::Endpoint(ep), all());
|
||||||
|
|
||||||
|
k.switch_to_thread_for_test(server);
|
||||||
|
assert!(matches!(
|
||||||
|
k.handle_syscall(Syscall::Receive { endpoint: slot }),
|
||||||
|
SyscallOutcome::BlockCaller(_)
|
||||||
|
));
|
||||||
|
let _ = k
|
||||||
|
.pick_next_thread()
|
||||||
|
.map(|t| k.set_thread_running(t, types::id::CoreId::from_raw(0)));
|
||||||
|
assert_eq!(k.current_thread(), Some(client));
|
||||||
|
|
||||||
|
let rounds = 20_000;
|
||||||
|
let start = Instant::now();
|
||||||
|
for _ in 0..rounds {
|
||||||
|
let out = k.handle_syscall(Syscall::Call {
|
||||||
|
endpoint: slot,
|
||||||
|
message: Message::empty(),
|
||||||
|
});
|
||||||
|
assert!(matches!(out, SyscallOutcome::SwitchTo(t) if t == server));
|
||||||
|
let out = k.handle_syscall(Syscall::ReplyRecv {
|
||||||
|
endpoint: slot,
|
||||||
|
message: Message::empty(),
|
||||||
|
});
|
||||||
|
assert!(matches!(out, SyscallOutcome::BlockCaller(_)));
|
||||||
|
let next = k.pick_next_thread().unwrap();
|
||||||
|
k.set_thread_running(next, types::id::CoreId::from_raw(0))
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(next, client);
|
||||||
|
}
|
||||||
|
report("Call + ReplyRecv round trip", rounds, start);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn bench_create_spawn_kill() {
|
||||||
|
let (mut k, _) = kernel_with_process();
|
||||||
|
let rounds = 2_000;
|
||||||
|
let start = Instant::now();
|
||||||
|
for _ in 0..rounds {
|
||||||
|
let pid = k.create_process_internal().unwrap();
|
||||||
|
k.spawn_thread_internal(pid).unwrap();
|
||||||
|
k.destroy_process(pid);
|
||||||
|
}
|
||||||
|
report("create process + spawn thread + destroy", rounds, start);
|
||||||
|
}
|
||||||
@@ -93,12 +93,17 @@ impl Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sorted, so a seed always means the same run (HashMap order is
|
||||||
|
/// random per process).
|
||||||
fn held_by(&self, pid: ProcessId) -> Vec<(usize, CapSlot)> {
|
fn held_by(&self, pid: ProcessId) -> Vec<(usize, CapSlot)> {
|
||||||
self.alive
|
let mut held: Vec<(usize, CapSlot)> = self
|
||||||
|
.alive
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, (p, _))| *p == pid)
|
.filter(|(_, (p, _))| *p == pid)
|
||||||
.map(|(id, (_, s))| (*id, *s))
|
.map(|(id, (_, s))| (*id, *s))
|
||||||
.collect()
|
.collect();
|
||||||
|
held.sort_by_key(|(id, _)| *id);
|
||||||
|
held
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,7 +307,14 @@ fn newly_used(before: [bool; 64], after: [bool; 64]) -> CapSlot {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn derivation_tree_matches_the_model() {
|
fn derivation_tree_matches_the_model() {
|
||||||
for seed in [5u64, 0xBADC0DE, 31337, 0x5EED, 2024] {
|
let extra: u64 = std::env::var("MODEL_SEEDS")
|
||||||
|
.ok()
|
||||||
|
.and_then(|v| v.parse().ok())
|
||||||
|
.unwrap_or(0);
|
||||||
|
for seed in [5u64, 0xBADC0DE, 31337, 0x5EED, 2024]
|
||||||
|
.into_iter()
|
||||||
|
.chain(1000..1000 + extra)
|
||||||
|
{
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
let mut rng = Rng(seed);
|
let mut rng = Rng(seed);
|
||||||
let mut revokes = 0;
|
let mut revokes = 0;
|
||||||
@@ -314,6 +326,9 @@ fn derivation_tree_matches_the_model() {
|
|||||||
}
|
}
|
||||||
world.check();
|
world.check();
|
||||||
}
|
}
|
||||||
|
if seed >= 1000 {
|
||||||
|
continue; // extra seeds hunt for mismatches, not coverage
|
||||||
|
}
|
||||||
assert!(
|
assert!(
|
||||||
revokes > 20,
|
revokes > 20,
|
||||||
"seed {seed:#x}: too little deletion exercised"
|
"seed {seed:#x}: too little deletion exercised"
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{SyscallOutcome, SyscallResult};
|
use crate::{SyscallOutcome, SyscallResult};
|
||||||
use domain::syscall::Syscall;
|
use domain::syscall::Syscall;
|
||||||
|
use platform::Platform;
|
||||||
use types::address::VirtualAddress;
|
use types::address::VirtualAddress;
|
||||||
use types::bounded::PageCount;
|
use types::bounded::PageCount;
|
||||||
|
use types::mapping::PagePerms;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn request_pages_returns_user_va() {
|
fn request_pages_returns_user_va() {
|
||||||
@@ -53,3 +55,78 @@ fn release_pages_returns_the_data_pages() {
|
|||||||
assert_eq!(k.free_page_count(), free_before + 4);
|
assert_eq!(k.free_page_count(), free_before + 4);
|
||||||
assert_eq!(k.page_usage(pid), used_before - 4);
|
assert_eq!(k.page_usage(pid), used_before - 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn request(k: &mut Kernel<MockPlatform>, pages: u16) -> Option<usize> {
|
||||||
|
match k.handle_syscall(Syscall::RequestPages {
|
||||||
|
count: PageCount::new(pages).unwrap(),
|
||||||
|
}) {
|
||||||
|
SyscallOutcome::Complete(SyscallResult::Value(va)) => Some(va),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_lying_page_table_cannot_hand_out_another_process_frame() {
|
||||||
|
// The kernel finds a page through the platform's page table but only
|
||||||
|
// believes the answer if its own frame table agrees.
|
||||||
|
let (mut k, a) = kernel_with_process();
|
||||||
|
let b = k.create_process_internal().unwrap();
|
||||||
|
k.map_fresh_range(
|
||||||
|
b,
|
||||||
|
VirtualAddress::from_raw(0x50000),
|
||||||
|
1,
|
||||||
|
PagePerms::ReadWrite,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let b_frame = k
|
||||||
|
.user_page_frame(b, VirtualAddress::from_raw(0x50000))
|
||||||
|
.unwrap();
|
||||||
|
let pages_before = k.page_usage(b);
|
||||||
|
|
||||||
|
// A's page at 0x40000 is claimed to be B's frame.
|
||||||
|
k.platform_mut().lie_about(a, 0x40000, b_frame.raw());
|
||||||
|
assert_eq!(
|
||||||
|
k.user_page_frame(a, VirtualAddress::from_raw(0x40000)),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
let out = k.handle_syscall(Syscall::ReleasePages {
|
||||||
|
addr: VirtualAddress::from_raw(0x40000),
|
||||||
|
count: PageCount::new(1).unwrap(),
|
||||||
|
});
|
||||||
|
assert!(matches!(
|
||||||
|
out,
|
||||||
|
SyscallOutcome::Complete(SyscallResult::Error(_))
|
||||||
|
));
|
||||||
|
assert_eq!(
|
||||||
|
k.user_page_frame(b, VirtualAddress::from_raw(0x50000)),
|
||||||
|
Some(b_frame)
|
||||||
|
);
|
||||||
|
assert_eq!(k.page_usage(b), pages_before);
|
||||||
|
|
||||||
|
// Nor is B's frame A's under a lie about the very page B uses.
|
||||||
|
k.platform_mut().lie_about(a, 0x50000, b_frame.raw());
|
||||||
|
assert_eq!(
|
||||||
|
k.user_page_frame(a, VirtualAddress::from_raw(0x50000)),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
// And a lie about the page number of B's own frame is caught too.
|
||||||
|
k.platform_mut().lie_about(b, 0x60000, b_frame.raw());
|
||||||
|
assert_eq!(
|
||||||
|
k.user_page_frame(b, VirtualAddress::from_raw(0x60000)),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_heap_search_wraps_back_to_the_bottom() {
|
||||||
|
let (mut k, pid) = kernel_with_process();
|
||||||
|
let first = request(&mut k, 1).unwrap();
|
||||||
|
k.advance_heap_cursor(
|
||||||
|
pid,
|
||||||
|
VirtualAddress::from_raw(MockPlatform::USER_VA_END - 4096),
|
||||||
|
);
|
||||||
|
let top = request(&mut k, 1).expect("the last page of the window");
|
||||||
|
assert_eq!(top, MockPlatform::USER_VA_END - 4096);
|
||||||
|
let again = request(&mut k, 2).expect("wraps to the bottom");
|
||||||
|
assert!(again < top && again != first);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
extern crate std;
|
||||||
|
|
||||||
use platform::{FrameSource, MapError, Platform};
|
use platform::{FrameSource, MapError, Platform};
|
||||||
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use types::{
|
use types::{
|
||||||
address::{EntryPoint, PhysicalAddress, StackPointer, VirtualAddress},
|
address::{EntryPoint, PhysicalAddress, StackPointer, VirtualAddress},
|
||||||
id::{CoreId, IrqId, ProcessId},
|
id::{CoreId, IrqId, ProcessId},
|
||||||
@@ -6,27 +9,10 @@ use types::{
|
|||||||
time::{Ticks, TimerDuration},
|
time::{Ticks, TimerDuration},
|
||||||
};
|
};
|
||||||
|
|
||||||
const MAX_MAPPINGS: usize = 2048;
|
|
||||||
const MAX_TABLES: usize = 256;
|
|
||||||
const MAX_SPACES: usize = 64;
|
const MAX_SPACES: usize = 64;
|
||||||
const MAX_ZEROED: usize = 2048;
|
|
||||||
// Each 2 MiB of user VA needs one leaf table, like Sv39.
|
// Each 2 MiB of user VA needs one leaf table, like Sv39.
|
||||||
const TABLE_SPAN: usize = 2 * 1024 * 1024;
|
const TABLE_SPAN: usize = 2 * 1024 * 1024;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
struct PageMapping {
|
|
||||||
process: ProcessId,
|
|
||||||
va: usize,
|
|
||||||
pa: usize,
|
|
||||||
perms: PagePerms,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
struct LeafTable {
|
|
||||||
process: ProcessId,
|
|
||||||
span: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Records what the "hardware" would hold so tests can check it against
|
/// Records what the "hardware" would hold so tests can check it against
|
||||||
/// the kernel's own bookkeeping.
|
/// the kernel's own bookkeeping.
|
||||||
pub struct MockPlatform {
|
pub struct MockPlatform {
|
||||||
@@ -34,9 +20,15 @@ pub struct MockPlatform {
|
|||||||
core: CoreId,
|
core: CoreId,
|
||||||
cores: u8,
|
cores: u8,
|
||||||
spaces: [bool; MAX_SPACES],
|
spaces: [bool; MAX_SPACES],
|
||||||
mappings: [Option<PageMapping>; MAX_MAPPINGS],
|
/// (process, va) -> (pa, perms). Ordered, so iteration is the same
|
||||||
tables: [Option<LeafTable>; MAX_TABLES],
|
/// on every run.
|
||||||
zeroed: [Option<usize>; MAX_ZEROED],
|
mappings: BTreeMap<(u16, usize), (usize, PagePerms)>,
|
||||||
|
/// (process, 2 MiB span) with a leaf table.
|
||||||
|
tables: BTreeSet<(u16, usize)>,
|
||||||
|
zeroed: BTreeSet<usize>,
|
||||||
|
/// Answers `translate` gives instead of the truth, to test that the
|
||||||
|
/// kernel never trusts them.
|
||||||
|
lies: BTreeMap<(u16, usize), usize>,
|
||||||
/// Cores an IPI was sent to and that have not taken it yet.
|
/// Cores an IPI was sent to and that have not taken it yet.
|
||||||
ipis: [bool; 8],
|
ipis: [bool; 8],
|
||||||
}
|
}
|
||||||
@@ -48,9 +40,10 @@ impl MockPlatform {
|
|||||||
core: CoreId::from_raw(0),
|
core: CoreId::from_raw(0),
|
||||||
cores: num_cores,
|
cores: num_cores,
|
||||||
spaces: [false; MAX_SPACES],
|
spaces: [false; MAX_SPACES],
|
||||||
mappings: [None; MAX_MAPPINGS],
|
mappings: BTreeMap::new(),
|
||||||
tables: [None; MAX_TABLES],
|
tables: BTreeSet::new(),
|
||||||
zeroed: [None; MAX_ZEROED],
|
zeroed: BTreeSet::new(),
|
||||||
|
lies: BTreeMap::new(),
|
||||||
ipis: [false; 8],
|
ipis: [false; 8],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,34 +67,25 @@ impl MockPlatform {
|
|||||||
|
|
||||||
/// Physical address `va` is mapped to in `process`, as the "hardware" sees it.
|
/// Physical address `va` is mapped to in `process`, as the "hardware" sees it.
|
||||||
pub fn mapping(&self, process: ProcessId, va: usize) -> Option<usize> {
|
pub fn mapping(&self, process: ProcessId, va: usize) -> Option<usize> {
|
||||||
self.mappings
|
self.mappings.get(&(process.raw(), va)).map(|(pa, _)| *pa)
|
||||||
.iter()
|
|
||||||
.flatten()
|
|
||||||
.find(|m| m.process == process && m.va == va)
|
|
||||||
.map(|m| m.pa)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Every live mapping as (process, va, pa).
|
/// Every live mapping as (process, va, pa).
|
||||||
pub fn mappings(&self) -> impl Iterator<Item = (ProcessId, usize, usize)> + '_ {
|
pub fn mappings(&self) -> impl Iterator<Item = (ProcessId, usize, usize)> + '_ {
|
||||||
self.mappings
|
self.mappings
|
||||||
.iter()
|
.iter()
|
||||||
.flatten()
|
.map(|((p, va), (pa, _))| (ProcessId::from_raw(*p), *va, *pa))
|
||||||
.map(|m| (m.process, m.va, m.pa))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn perms(&self, process: ProcessId, va: usize) -> Option<PagePerms> {
|
pub fn perms(&self, process: ProcessId, va: usize) -> Option<PagePerms> {
|
||||||
self.mappings
|
self.mappings
|
||||||
.iter()
|
.get(&(process.raw(), va))
|
||||||
.flatten()
|
.map(|(_, perms)| *perms)
|
||||||
.find(|m| m.process == process && m.va == va)
|
|
||||||
.map(|m| m.perms)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mapping_count(&self, process: ProcessId) -> usize {
|
pub fn mapping_count(&self, process: ProcessId) -> usize {
|
||||||
self.mappings
|
self.mappings
|
||||||
.iter()
|
.range((process.raw(), 0)..=(process.raw(), usize::MAX))
|
||||||
.flatten()
|
|
||||||
.filter(|m| m.process == process)
|
|
||||||
.count()
|
.count()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +94,12 @@ impl MockPlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn was_zeroed(&self, pa: usize) -> bool {
|
pub fn was_zeroed(&self, pa: usize) -> bool {
|
||||||
self.zeroed.contains(&Some(pa))
|
self.zeroed.contains(&pa)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// From now on `translate(process, va)` answers `pa`.
|
||||||
|
pub fn lie_about(&mut self, process: ProcessId, va: usize, pa: usize) {
|
||||||
|
self.lies.insert((process.raw(), va), pa);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_ticks(&mut self, ticks: Ticks) {
|
pub fn set_ticks(&mut self, ticks: Ticks) {
|
||||||
@@ -135,16 +124,9 @@ impl Platform for MockPlatform {
|
|||||||
|
|
||||||
fn destroy_address_space(&mut self, process: ProcessId) {
|
fn destroy_address_space(&mut self, process: ProcessId) {
|
||||||
self.spaces[process.raw() as usize] = false;
|
self.spaces[process.raw() as usize] = false;
|
||||||
for slot in &mut self.mappings {
|
let p = process.raw();
|
||||||
if slot.is_some_and(|m| m.process == process) {
|
self.mappings.retain(|(owner, _), _| *owner != p);
|
||||||
*slot = None;
|
self.tables.retain(|(owner, _)| *owner != p);
|
||||||
}
|
|
||||||
}
|
|
||||||
for slot in &mut self.tables {
|
|
||||||
if slot.is_some_and(|t| t.process == process) {
|
|
||||||
*slot = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_page(
|
fn map_page(
|
||||||
@@ -163,59 +145,28 @@ impl Platform for MockPlatform {
|
|||||||
return Err(MapError::InvalidAddress);
|
return Err(MapError::InvalidAddress);
|
||||||
}
|
}
|
||||||
let span = va / TABLE_SPAN;
|
let span = va / TABLE_SPAN;
|
||||||
let have_table = self
|
if !self.tables.contains(&(process.raw(), span)) {
|
||||||
.tables
|
|
||||||
.iter()
|
|
||||||
.flatten()
|
|
||||||
.any(|t| t.process == process && t.span == span);
|
|
||||||
if !have_table {
|
|
||||||
frames.alloc_table_frame().ok_or(MapError::OutOfFrames)?;
|
frames.alloc_table_frame().ok_or(MapError::OutOfFrames)?;
|
||||||
let slot = self
|
self.tables.insert((process.raw(), span));
|
||||||
.tables
|
|
||||||
.iter_mut()
|
|
||||||
.find(|t| t.is_none())
|
|
||||||
.expect("mock table slots");
|
|
||||||
*slot = Some(LeafTable { process, span });
|
|
||||||
}
|
|
||||||
let entry = PageMapping {
|
|
||||||
process,
|
|
||||||
va,
|
|
||||||
pa: pa.raw(),
|
|
||||||
perms,
|
|
||||||
};
|
|
||||||
match self
|
|
||||||
.mappings
|
|
||||||
.iter_mut()
|
|
||||||
.find(|m| m.is_some_and(|m| m.process == process && m.va == va))
|
|
||||||
{
|
|
||||||
Some(existing) => *existing = Some(entry),
|
|
||||||
None => {
|
|
||||||
let slot = self
|
|
||||||
.mappings
|
|
||||||
.iter_mut()
|
|
||||||
.find(|m| m.is_none())
|
|
||||||
.expect("mock mapping slots");
|
|
||||||
*slot = Some(entry);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
self.mappings.insert((process.raw(), va), (pa.raw(), perms));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unmap_page(&mut self, process: ProcessId, va: VirtualAddress) {
|
fn unmap_page(&mut self, process: ProcessId, va: VirtualAddress) {
|
||||||
for slot in &mut self.mappings {
|
self.mappings.remove(&(process.raw(), va.raw()));
|
||||||
if slot.is_some_and(|m| m.process == process && m.va == va.raw()) {
|
}
|
||||||
*slot = None;
|
|
||||||
}
|
fn translate(&self, process: ProcessId, va: VirtualAddress) -> Option<PhysicalAddress> {
|
||||||
}
|
self.lies
|
||||||
|
.get(&(process.raw(), va.raw()))
|
||||||
|
.copied()
|
||||||
|
.or_else(|| self.mapping(process, va.raw()))
|
||||||
|
.map(PhysicalAddress::from_raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn zero_frame(&mut self, pa: PhysicalAddress) {
|
fn zero_frame(&mut self, pa: PhysicalAddress) {
|
||||||
if self.zeroed.contains(&Some(pa.raw())) {
|
self.zeroed.insert(pa.raw());
|
||||||
return;
|
|
||||||
}
|
|
||||||
if let Some(slot) = self.zeroed.iter_mut().find(|z| z.is_none()) {
|
|
||||||
*slot = Some(pa.raw());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enable_user_mode(&mut self, _entry: EntryPoint, _stack: StackPointer) -> ! {
|
fn enable_user_mode(&mut self, _entry: EntryPoint, _stack: StackPointer) -> ! {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ mod helpers;
|
|||||||
mod mock_platform;
|
mod mock_platform;
|
||||||
|
|
||||||
mod abi;
|
mod abi;
|
||||||
|
mod bench;
|
||||||
mod board;
|
mod board;
|
||||||
mod capability;
|
mod capability;
|
||||||
mod captree_model;
|
mod captree_model;
|
||||||
|
|||||||
@@ -96,6 +96,31 @@ impl Kernel<MockPlatform> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// The tree's links say exactly what the capabilities' parents say.
|
||||||
|
for i in 0..crate::captree::ALL_CAPS {
|
||||||
|
let pid = ProcessId::from_raw((i / domain::capability::MAX_CAPS_PER_PROCESS) as u16);
|
||||||
|
let slot = CapSlot::new((i % domain::capability::MAX_CAPS_PER_PROCESS) as u8).unwrap();
|
||||||
|
let cap = self
|
||||||
|
.processes
|
||||||
|
.get(pid)
|
||||||
|
.ok()
|
||||||
|
.and_then(|p| p.capabilities().get(slot).ok());
|
||||||
|
if cap.is_some() != self.tree.is_live(i) {
|
||||||
|
return Err(format!(
|
||||||
|
"{pid:?}/{slot:?}: tree and table disagree on liveness"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let recorded = cap.and_then(|c| c.parent()).map(|r| {
|
||||||
|
r.process.raw() as usize * domain::capability::MAX_CAPS_PER_PROCESS
|
||||||
|
+ r.slot.value() as usize
|
||||||
|
});
|
||||||
|
if cap.is_some() && recorded != self.tree.parent(i) {
|
||||||
|
return Err(format!(
|
||||||
|
"{pid:?}/{slot:?}: tree parent {:?}, cap parent {recorded:?}",
|
||||||
|
self.tree.parent(i)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
for (_, region) in self.regions.iter() {
|
for (_, region) in self.regions.iter() {
|
||||||
let has = |want| region.mappings().any(|m| m.perms == want);
|
let has = |want| region.mappings().any(|m| m.perms == want);
|
||||||
if has(PagePerms::ReadWrite) && has(PagePerms::ReadExecute) {
|
if has(PagePerms::ReadWrite) && has(PagePerms::ReadExecute) {
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ pub trait Platform {
|
|||||||
) -> Result<(), MapError>;
|
) -> Result<(), MapError>;
|
||||||
/// Removes the mapping at `va` and flushes it from the TLB.
|
/// Removes the mapping at `va` and flushes it from the TLB.
|
||||||
fn unmap_page(&mut self, process: ProcessId, va: VirtualAddress);
|
fn unmap_page(&mut self, process: ProcessId, va: VirtualAddress);
|
||||||
|
/// The frame `va` is mapped to in `process`, if any. Only a lookup:
|
||||||
|
/// the kernel confirms every answer against its own frame table, so a
|
||||||
|
/// wrong answer can make the kernel refuse but never trust a frame.
|
||||||
|
fn translate(&self, process: ProcessId, va: VirtualAddress) -> Option<PhysicalAddress>;
|
||||||
/// Fills a frame with zeros before it is handed to a process.
|
/// Fills a frame with zeros before it is handed to a process.
|
||||||
fn zero_frame(&mut self, pa: PhysicalAddress);
|
fn zero_frame(&mut self, pa: PhysicalAddress);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user