feat(kernel): per-process budgets (pages, endpoints, notifs, regions) carved from creator, returned on death; init owns pool
This commit is contained in:
@@ -2,6 +2,7 @@ use crate::hw::uart::Uart;
|
||||
use crate::hw::virtio_blk::VirtioBlk;
|
||||
use crate::{boot_fs, mmu};
|
||||
use types::address::VirtualAddress;
|
||||
use types::budget::Limits;
|
||||
use types::id::{ProcessId, ThreadId};
|
||||
use types::mapping::PagePerms;
|
||||
|
||||
@@ -52,14 +53,38 @@ pub fn load_init(uart: &Uart, blk: &mut VirtioBlk) -> LoadedProcess {
|
||||
LoadedProcess { pid, tid }
|
||||
}
|
||||
|
||||
/// The servers boot loads, their budgets carved from init's.
|
||||
pub const STORAGE: Limits = Limits {
|
||||
pages: 256,
|
||||
endpoints: 2,
|
||||
notifications: 2,
|
||||
// A buffer per client (8) and the disk's DMA.
|
||||
regions: 12,
|
||||
};
|
||||
/// The framebuffer alone is ~1000 pages at 1280x800.
|
||||
pub const DISPLAY: Limits = Limits {
|
||||
pages: 1280,
|
||||
endpoints: 2,
|
||||
notifications: 2,
|
||||
regions: 4,
|
||||
};
|
||||
pub const INPUT: Limits = Limits {
|
||||
pages: 64,
|
||||
endpoints: 2,
|
||||
notifications: 2,
|
||||
regions: 4,
|
||||
};
|
||||
|
||||
pub fn load_server(
|
||||
uart: &Uart,
|
||||
blk: &mut VirtioBlk,
|
||||
init: ProcessId,
|
||||
filename: &[u8],
|
||||
label: &str,
|
||||
limits: Limits,
|
||||
) -> LoadedProcess {
|
||||
let pid = kern()
|
||||
.create_process_internal()
|
||||
.create_process_internal(init, limits)
|
||||
.unwrap_or_else(|| crate::halt(uart, "process create failed"));
|
||||
let entry = load_elf(uart, blk, pid, filename);
|
||||
let tid = kern()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Boot-time userspace setup: the display's page quota and init's BootInfo page.
|
||||
// Boot-time userspace setup: init's BootInfo page.
|
||||
|
||||
use crate::hw::uart::Uart;
|
||||
use types::address::VirtualAddress;
|
||||
@@ -6,20 +6,10 @@ use types::bootinfo::{BOOT_INFO_VA, BootDevice, BootInfo, MAGIC, MAX_BOOT_DEVICE
|
||||
use types::id::ProcessId;
|
||||
use types::mapping::PagePerms;
|
||||
|
||||
/// The framebuffer alone is ~1000 pages at 1280x800.
|
||||
const DISPLAY_PAGE_LIMIT: usize = 2048;
|
||||
|
||||
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
||||
unsafe { &mut *crate::kernel_ptr() }
|
||||
}
|
||||
|
||||
/// Lets the display server hold a framebuffer.
|
||||
pub(crate) fn display_quota(uart: &Uart, display: ProcessId) {
|
||||
kern()
|
||||
.set_page_limit(display, DISPLAY_PAGE_LIMIT)
|
||||
.unwrap_or_else(|_| crate::halt(uart, "display quota"));
|
||||
}
|
||||
|
||||
/// Tells init, in a read-only page at BOOT_INFO_VA, which of its slots hold
|
||||
/// each board device's capabilities.
|
||||
pub(crate) fn boot_info(
|
||||
|
||||
@@ -40,15 +40,29 @@ extern "C" fn kmain(hart: usize) -> ! {
|
||||
boot::hardware::probe_block_device(&uart).unwrap_or_else(|| halt(&uart, "no block device"));
|
||||
|
||||
let init = boot::loader::load_init(&uart, &mut blk);
|
||||
let storage = boot::loader::load_server(&uart, &mut blk, b"storage.elf", "storage");
|
||||
let display = boot::loader::load_server(&uart, &mut blk, b"display.elf", "display");
|
||||
let input = boot::loader::load_server(&uart, &mut blk, b"input.elf", "input");
|
||||
use boot::loader::{DISPLAY, INPUT, STORAGE, load_server};
|
||||
let storage = load_server(
|
||||
&uart,
|
||||
&mut blk,
|
||||
init.pid,
|
||||
b"storage.elf",
|
||||
"storage",
|
||||
STORAGE,
|
||||
);
|
||||
let display = load_server(
|
||||
&uart,
|
||||
&mut blk,
|
||||
init.pid,
|
||||
b"display.elf",
|
||||
"display",
|
||||
DISPLAY,
|
||||
);
|
||||
let input = load_server(&uart, &mut blk, init.pid, b"input.elf", "input", INPUT);
|
||||
|
||||
let board = board::devices().unwrap_or_else(|| halt(&uart, "bad board table"));
|
||||
kern_mut()
|
||||
.register_board(init.pid, &board)
|
||||
.unwrap_or_else(|_| halt(&uart, "board registration failed"));
|
||||
devices::display_quota(&uart, display.pid);
|
||||
boot::caps::grant_all(&uart, init.pid, storage.pid, display.pid, input.pid);
|
||||
// Last, so the servers' fixed slots stay where they expect them.
|
||||
boot::caps::grant_console(&uart, &[storage, display, input]);
|
||||
|
||||
80
crates/domain/src/budget.rs
Normal file
80
crates/domain/src/budget.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
//! What a process may hold: frames and kernel objects. A child's limits
|
||||
//! are carved from its creator's unused budget and go back when it dies,
|
||||
//! so the limits of all live processes never add up to more than init got.
|
||||
|
||||
use crate::memory::quota::Quota;
|
||||
pub use types::budget::Limits;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "budget_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Budget {
|
||||
pub pages: Quota,
|
||||
pub endpoints: Quota,
|
||||
pub notifications: Quota,
|
||||
pub regions: Quota,
|
||||
}
|
||||
|
||||
impl Budget {
|
||||
pub const EMPTY: Self = Self::new(Limits {
|
||||
pages: 0,
|
||||
endpoints: 0,
|
||||
notifications: 0,
|
||||
regions: 0,
|
||||
});
|
||||
|
||||
pub const fn new(limits: Limits) -> Self {
|
||||
Self {
|
||||
pages: Quota::new(limits.pages),
|
||||
endpoints: Quota::new(limits.endpoints),
|
||||
notifications: Quota::new(limits.notifications),
|
||||
regions: Quota::new(limits.regions),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn limits(&self) -> Limits {
|
||||
Limits {
|
||||
pages: self.pages.limit(),
|
||||
endpoints: self.endpoints.limit(),
|
||||
notifications: self.notifications.limit(),
|
||||
regions: self.regions.limit(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parts(&mut self) -> [&mut Quota; 4] {
|
||||
[
|
||||
&mut self.pages,
|
||||
&mut self.endpoints,
|
||||
&mut self.notifications,
|
||||
&mut self.regions,
|
||||
]
|
||||
}
|
||||
|
||||
/// Carves `child` out of the unused budget: all of it or nothing.
|
||||
pub fn carve(&mut self, child: Limits) -> bool {
|
||||
let amounts = amounts(child);
|
||||
let fits = self.parts().iter().zip(amounts).all(|(q, n)| {
|
||||
q.limit()
|
||||
.checked_sub(n)
|
||||
.is_some_and(|rest| rest >= q.used())
|
||||
});
|
||||
if fits {
|
||||
for (q, n) in self.parts().into_iter().zip(amounts) {
|
||||
q.carve(n);
|
||||
}
|
||||
}
|
||||
fits
|
||||
}
|
||||
|
||||
pub fn refund(&mut self, child: Limits) {
|
||||
for (q, n) in self.parts().into_iter().zip(amounts(child)) {
|
||||
q.refund(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn amounts(l: Limits) -> [usize; 4] {
|
||||
[l.pages, l.endpoints, l.notifications, l.regions]
|
||||
}
|
||||
35
crates/domain/src/budget_tests.rs
Normal file
35
crates/domain/src/budget_tests.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use super::{Budget, Limits};
|
||||
|
||||
const L: Limits = Limits {
|
||||
pages: 10,
|
||||
endpoints: 2,
|
||||
notifications: 2,
|
||||
regions: 1,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn a_carve_takes_all_parts_or_none() {
|
||||
let mut b = Budget::new(L);
|
||||
b.endpoints.charge(2);
|
||||
let child = Limits {
|
||||
pages: 1,
|
||||
endpoints: 1,
|
||||
..Limits::default()
|
||||
};
|
||||
assert!(!b.carve(child));
|
||||
assert_eq!(b.limits(), L);
|
||||
assert!(b.carve(Limits {
|
||||
endpoints: 0,
|
||||
..child
|
||||
}));
|
||||
assert_eq!(b.limits().pages, 9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_refund_restores_what_was_carved() {
|
||||
let mut b = Budget::new(L);
|
||||
assert!(b.carve(L));
|
||||
assert_eq!(b.limits(), Limits::default());
|
||||
b.refund(L);
|
||||
assert_eq!(b.limits(), L);
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
#[cfg(kani)]
|
||||
pub mod proofs;
|
||||
|
||||
pub mod budget;
|
||||
pub mod capability;
|
||||
pub mod endpoint;
|
||||
pub mod error;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/// Per-process page budget. Every frame charged to a process (user pages
|
||||
/// and its page tables) counts against it.
|
||||
/// A count against a limit: one of a process's budgets (see `budget`).
|
||||
/// For pages, every frame charged to a process (user pages and its page
|
||||
/// tables) counts against it.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Quota {
|
||||
used: usize,
|
||||
@@ -7,8 +8,6 @@ pub struct Quota {
|
||||
}
|
||||
|
||||
impl Quota {
|
||||
pub const DEFAULT_LIMIT: usize = 1024;
|
||||
|
||||
pub const fn new(limit: usize) -> Self {
|
||||
Self { used: 0, limit }
|
||||
}
|
||||
@@ -41,17 +40,19 @@ impl Quota {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_limit(&mut self, limit: usize) -> bool {
|
||||
if limit < self.used {
|
||||
return false;
|
||||
/// Gives up `amount` of the unused limit, for a child to hold.
|
||||
pub fn carve(&mut self, amount: usize) -> bool {
|
||||
match self.limit.checked_sub(amount) {
|
||||
Some(rest) if rest >= self.used => {
|
||||
self.limit = rest;
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
self.limit = limit;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Quota {
|
||||
fn default() -> Self {
|
||||
Self::new(Self::DEFAULT_LIMIT)
|
||||
/// Takes back a limit carved out earlier.
|
||||
pub fn refund(&mut self, amount: usize) {
|
||||
self.limit = self.limit.saturating_add(amount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,10 +28,13 @@ fn uncharge_cannot_underflow() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn limit_can_be_raised_but_not_below_usage() {
|
||||
fn only_the_unused_limit_can_be_carved() {
|
||||
let mut q = Quota::new(4);
|
||||
q.charge(3);
|
||||
assert!(!q.set_limit(2));
|
||||
assert!(q.set_limit(10));
|
||||
assert!(q.charge(7));
|
||||
assert!(!q.carve(2));
|
||||
assert_eq!(q.limit(), 4);
|
||||
assert!(q.carve(1));
|
||||
assert!(!q.charge(1));
|
||||
q.refund(1);
|
||||
assert!(q.charge(1));
|
||||
}
|
||||
|
||||
33
crates/domain/src/proofs/budget.rs
Normal file
33
crates/domain/src/proofs/budget.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use crate::budget::{Budget, Limits};
|
||||
|
||||
fn any_limits() -> Limits {
|
||||
let l = Limits {
|
||||
pages: kani::any(),
|
||||
endpoints: kani::any(),
|
||||
notifications: kani::any(),
|
||||
regions: kani::any(),
|
||||
};
|
||||
kani::assume(l.pages < 1 << 20 && l.endpoints < 1 << 20);
|
||||
kani::assume(l.notifications < 1 << 20 && l.regions < 1 << 20);
|
||||
l
|
||||
}
|
||||
|
||||
/// A carve never lets parent and child together exceed what the parent
|
||||
/// had, never strands the parent's usage, and a refund undoes it.
|
||||
#[kani::proof]
|
||||
fn carving_conserves_the_budget() {
|
||||
let (have, child) = (any_limits(), any_limits());
|
||||
let used: usize = kani::any();
|
||||
let mut b = Budget::new(have);
|
||||
b.pages.charge(used);
|
||||
let before = b;
|
||||
if b.carve(child) {
|
||||
assert_eq!(b.limits().pages + child.pages, have.pages);
|
||||
assert_eq!(b.limits().regions + child.regions, have.regions);
|
||||
assert!(b.pages.used() <= b.pages.limit());
|
||||
b.refund(child);
|
||||
assert_eq!(b, before);
|
||||
} else {
|
||||
assert_eq!(b, before);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
mod budget;
|
||||
mod capability;
|
||||
mod delegation;
|
||||
mod thread;
|
||||
|
||||
@@ -62,9 +62,11 @@ pub enum Syscall {
|
||||
AcknowledgeInterrupt {
|
||||
irq: CapSlot,
|
||||
},
|
||||
/// The new process's `limits` are carved from the caller's budget.
|
||||
CreateProcess {
|
||||
create_cap: CapSlot,
|
||||
image: DataRef,
|
||||
limits: crate::budget::Limits,
|
||||
},
|
||||
Kill {
|
||||
process: CapSlot,
|
||||
|
||||
@@ -7,6 +7,7 @@ mod selftest;
|
||||
|
||||
use types::address::{EntryPoint, StackPointer, VirtualAddress};
|
||||
use types::bounded::{CapSlot, PageCount};
|
||||
use types::budget::Limits;
|
||||
use types::permission::{Permission, PermissionSet};
|
||||
use libkos::fs::Storage;
|
||||
use libkos::syscall;
|
||||
@@ -42,7 +43,12 @@ pub(crate) struct LoadedProcess {
|
||||
pub(crate) entry_point: EntryPoint,
|
||||
}
|
||||
|
||||
pub(crate) fn load_binary(storage: &mut Storage, scratch: &mut [u8], filename: &[u8]) -> Option<LoadedProcess> {
|
||||
pub(crate) fn load_binary(
|
||||
storage: &mut Storage,
|
||||
scratch: &mut [u8],
|
||||
filename: &[u8],
|
||||
limits: Limits,
|
||||
) -> Option<LoadedProcess> {
|
||||
let file_size = read_file(storage, scratch, filename)?;
|
||||
let elf_data = &scratch[..file_size];
|
||||
let elf_info = match elf::parse(elf_data) {
|
||||
@@ -50,7 +56,7 @@ pub(crate) fn load_binary(storage: &mut Storage, scratch: &mut [u8], filename: &
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
let process_cap = syscall::create_process(CREATE_CAP)?;
|
||||
let process_cap = syscall::create_process(CREATE_CAP, limits)?;
|
||||
|
||||
for segment in elf_info.segments() {
|
||||
let page_start = segment.vaddr & !0xFFF;
|
||||
@@ -102,14 +108,25 @@ pub(crate) fn load_binary(storage: &mut Storage, scratch: &mut [u8], filename: &
|
||||
struct ServerInfo {
|
||||
filename: &'static [u8],
|
||||
name: &'static str,
|
||||
limits: Limits,
|
||||
}
|
||||
|
||||
/// Enough for a small program: its image, stack and a little heap.
|
||||
pub(crate) const SMALL: Limits = Limits {
|
||||
pages: 64,
|
||||
endpoints: 2,
|
||||
notifications: 2,
|
||||
regions: 2,
|
||||
};
|
||||
|
||||
const SERVERS: &[ServerInfo] = &[
|
||||
ServerInfo { filename: b"log.elf", name: "log" },
|
||||
ServerInfo { filename: b"permis.elf", name: "permis" },
|
||||
ServerInfo { filename: b"session.elf", name: "session" },
|
||||
ServerInfo { filename: b"log.elf", name: "log", limits: SMALL },
|
||||
ServerInfo { filename: b"permis.elf", name: "permis", limits: SMALL },
|
||||
ServerInfo { filename: b"session.elf", name: "session", limits: SMALL },
|
||||
];
|
||||
|
||||
const SNAKE: Limits = Limits { pages: 128, ..SMALL };
|
||||
|
||||
pub fn run() -> ! {
|
||||
libkos::start::set_console(devices::console());
|
||||
libkos::println!("init: started (pid={:#x})", syscall::get_process_id().raw());
|
||||
@@ -142,7 +159,7 @@ pub fn run() -> ! {
|
||||
for server in SERVERS {
|
||||
libkos::print!("init: loading {}... ", server.name);
|
||||
|
||||
let loaded = match load_binary(&mut storage, scratch, server.filename) {
|
||||
let loaded = match load_binary(&mut storage, scratch, server.filename, server.limits) {
|
||||
Some(l) => l,
|
||||
None => {
|
||||
libkos::println!("SKIP");
|
||||
@@ -166,7 +183,7 @@ pub fn run() -> ! {
|
||||
}
|
||||
|
||||
libkos::print!("init: loading snake... ");
|
||||
if let Some(snake) = load_binary(&mut storage, scratch, b"snake.elf") {
|
||||
if let Some(snake) = load_binary(&mut storage, scratch, b"snake.elf", SNAKE) {
|
||||
// A client only calls: Execute, never Read, so it cannot pose as
|
||||
// the server and take other clients' requests.
|
||||
let client = PermissionSet::empty().with(Permission::Execute);
|
||||
|
||||
@@ -40,7 +40,7 @@ fn start_spinner(storage: &mut Storage, scratch: &mut [u8], page: CapSlot) -> Re
|
||||
for i in 0..=THREADS {
|
||||
word(i).store(0, Ordering::SeqCst);
|
||||
}
|
||||
let spinner = crate::load_binary(storage, scratch, b"smptest.elf").ok_or("cannot load smptest.elf")?;
|
||||
let spinner = crate::load_binary(storage, scratch, b"smptest.elf", crate::SMALL).ok_or("cannot load smptest.elf")?;
|
||||
let rw = PermissionSet::empty()
|
||||
.with(Permission::Read)
|
||||
.with(Permission::Write)
|
||||
|
||||
@@ -94,6 +94,12 @@ pub fn decode(nr: usize, a: &[usize; 6]) -> Option<Syscall> {
|
||||
SYS_CREATE_PROCESS => Syscall::CreateProcess {
|
||||
create_cap: slot(a[0])?,
|
||||
image: DataRef::empty(),
|
||||
limits: domain::budget::Limits {
|
||||
pages: a[1],
|
||||
endpoints: a[2],
|
||||
notifications: a[3],
|
||||
regions: a[4],
|
||||
},
|
||||
},
|
||||
SYS_KILL => Syscall::Kill {
|
||||
process: slot(a[0])?,
|
||||
|
||||
100
crates/kernel/src/budget.rs
Normal file
100
crates/kernel/src/budget.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
//! Per-process budgets (see `domain::budget`): carved from the creator,
|
||||
//! charged as a process takes frames and kernel objects, returned on death.
|
||||
|
||||
use domain::{
|
||||
budget::{Budget, Limits},
|
||||
capability::CapabilityTable,
|
||||
error::KernelError,
|
||||
memory::quota::Quota,
|
||||
};
|
||||
use platform::Platform;
|
||||
use types::id::ProcessId;
|
||||
|
||||
use crate::Kernel;
|
||||
|
||||
/// Picks the part of a budget an object counts against.
|
||||
pub(crate) type Part = fn(&mut Budget) -> &mut Quota;
|
||||
|
||||
pub(crate) const ENDPOINTS: Part = |b| &mut b.endpoints;
|
||||
pub(crate) const NOTIFICATIONS: Part = |b| &mut b.notifications;
|
||||
pub(crate) const REGIONS: Part = |b| &mut b.regions;
|
||||
|
||||
impl<P: Platform> Kernel<P> {
|
||||
pub(crate) fn budget_mut(&mut self, pid: ProcessId) -> Result<&mut Budget, KernelError> {
|
||||
self.budgets
|
||||
.get_mut(pid.raw() as usize)
|
||||
.ok_or(KernelError::InvalidProcess(pid))
|
||||
}
|
||||
|
||||
pub fn limits_of(&self, pid: ProcessId) -> Option<Limits> {
|
||||
self.processes.get(pid).ok()?;
|
||||
self.budgets.get(pid.raw() as usize).map(Budget::limits)
|
||||
}
|
||||
|
||||
/// A new process holding `limits`, carved from `parent`'s unused budget;
|
||||
/// only boot makes one without a parent.
|
||||
pub(crate) fn new_process(
|
||||
&mut self,
|
||||
parent: Option<ProcessId>,
|
||||
limits: Limits,
|
||||
) -> Result<ProcessId, KernelError> {
|
||||
if let Some(p) = parent {
|
||||
self.processes.get(p)?;
|
||||
if !self.budget_mut(p)?.carve(limits) {
|
||||
return Err(KernelError::OutOfMemory);
|
||||
}
|
||||
}
|
||||
let pid = match self.processes.create(CapabilityTable::new(), None) {
|
||||
Ok(pid) => pid,
|
||||
Err(e) => {
|
||||
if let Some(p) = parent {
|
||||
self.budget_mut(p)?.refund(limits);
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
*self.budget_mut(pid)? = Budget::new(limits);
|
||||
if let Some(slot) = self.parents.get_mut(pid.raw() as usize) {
|
||||
*slot = parent;
|
||||
}
|
||||
if let Err(e) = self.create_address_space(pid) {
|
||||
self.release_budget(pid);
|
||||
let _ = self.processes.destroy(pid);
|
||||
return Err(e);
|
||||
}
|
||||
Ok(pid)
|
||||
}
|
||||
|
||||
/// Gives a dead process's limits back to its parent; its children are
|
||||
/// carved from what it held, so they pass to that parent too.
|
||||
pub(crate) fn release_budget(&mut self, pid: ProcessId) {
|
||||
let i = pid.raw() as usize;
|
||||
let parent = self.parents.get_mut(i).and_then(Option::take);
|
||||
let limits = self
|
||||
.budgets
|
||||
.get_mut(i)
|
||||
.map(|b| core::mem::replace(b, Budget::EMPTY).limits());
|
||||
if let (Some(p), Some(limits)) = (parent, limits)
|
||||
&& let Ok(b) = self.budget_mut(p)
|
||||
{
|
||||
b.refund(limits);
|
||||
}
|
||||
for slot in self.parents.iter_mut().filter(|s| **s == Some(pid)) {
|
||||
*slot = parent;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn charge_object(&mut self, pid: ProcessId, part: Part) -> Result<(), KernelError> {
|
||||
if part(self.budget_mut(pid)?).charge(1) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(KernelError::NoFreeSlot)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn uncharge_object(&mut self, pid: ProcessId, part: Part) {
|
||||
if let Ok(b) = self.budget_mut(pid) {
|
||||
part(b).uncharge(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
pub mod abi;
|
||||
mod board;
|
||||
mod budget;
|
||||
mod captree;
|
||||
pub mod interrupt;
|
||||
mod memory;
|
||||
@@ -33,11 +34,12 @@ pub use syscall::{SyscallOutcome, SyscallResult};
|
||||
mod tests;
|
||||
|
||||
use domain::{
|
||||
capability::{Capability, CapabilityTable},
|
||||
budget::{Budget, Limits},
|
||||
capability::Capability,
|
||||
endpoint::EndpointTable,
|
||||
error::KernelError,
|
||||
irq::IrqRoutingTable,
|
||||
memory::{RegionTable, frame::FrameTable, quota::Quota},
|
||||
memory::{RegionTable, frame::FrameTable},
|
||||
notification::NotificationTable,
|
||||
process::{MAX_PROCESSES, ProcessTable},
|
||||
resource::Resource,
|
||||
@@ -61,7 +63,9 @@ pub struct Kernel<P: Platform> {
|
||||
endpoints: EndpointTable,
|
||||
scheduler: MultiCoreScheduler,
|
||||
frames: FrameTable,
|
||||
quotas: [Quota; MAX_PROCESSES],
|
||||
budgets: [Budget; MAX_PROCESSES],
|
||||
/// Whose budget each process was carved from (see budget.rs).
|
||||
parents: [Option<ProcessId>; MAX_PROCESSES],
|
||||
regions: RegionTable,
|
||||
irqs: IrqRoutingTable,
|
||||
notifications: NotificationTable,
|
||||
@@ -85,7 +89,8 @@ impl<P: Platform> Kernel<P> {
|
||||
endpoints: EndpointTable::new(),
|
||||
scheduler: MultiCoreScheduler::new(num_cores),
|
||||
frames: FrameTable::new(pool_base, pool_pages),
|
||||
quotas: [Quota::default(); MAX_PROCESSES],
|
||||
budgets: [Budget::EMPTY; MAX_PROCESSES],
|
||||
parents: [None; MAX_PROCESSES],
|
||||
regions: RegionTable::new(),
|
||||
irqs: IrqRoutingTable::new(),
|
||||
notifications: NotificationTable::new(),
|
||||
@@ -118,7 +123,8 @@ impl<P: Platform> Kernel<P> {
|
||||
addr_of_mut!((*slot).endpoints).write(EndpointTable::new());
|
||||
addr_of_mut!((*slot).scheduler).write(MultiCoreScheduler::new(num_cores));
|
||||
addr_of_mut!((*slot).frames).write(FrameTable::new(pool_base, pool_pages));
|
||||
addr_of_mut!((*slot).quotas).write([Quota::default(); MAX_PROCESSES]);
|
||||
addr_of_mut!((*slot).budgets).write([Budget::EMPTY; MAX_PROCESSES]);
|
||||
addr_of_mut!((*slot).parents).write([None; MAX_PROCESSES]);
|
||||
addr_of_mut!((*slot).regions).write(RegionTable::new());
|
||||
addr_of_mut!((*slot).irqs).write(IrqRoutingTable::new());
|
||||
addr_of_mut!((*slot).notifications).write(NotificationTable::new());
|
||||
@@ -135,11 +141,14 @@ impl<P: Platform> Kernel<P> {
|
||||
priority: Priority,
|
||||
time_slice: TimeSlice,
|
||||
) -> Result<(ProcessId, ThreadId), KernelError> {
|
||||
let pid = self.processes.create(CapabilityTable::new(), None)?;
|
||||
if let Err(e) = self.create_address_space(pid) {
|
||||
let _ = self.processes.destroy(pid);
|
||||
return Err(e);
|
||||
}
|
||||
// Init holds every frame and every kernel object to start with.
|
||||
let everything = Limits {
|
||||
pages: self.frames.free_count(),
|
||||
endpoints: domain::endpoint::MAX_ENDPOINTS,
|
||||
notifications: domain::notification::MAX_NOTIFICATIONS,
|
||||
regions: domain::memory::MAX_REGIONS,
|
||||
};
|
||||
let pid = self.new_process(None, everything)?;
|
||||
let tid = self.threads.spawn(pid, priority, time_slice, None)?;
|
||||
self.processes.get_mut(pid)?.add_thread(tid)?;
|
||||
|
||||
@@ -211,13 +220,13 @@ impl<P: Platform> Kernel<P> {
|
||||
self.scheduler.next(core)
|
||||
}
|
||||
|
||||
pub fn create_process_internal(&mut self) -> Option<ProcessId> {
|
||||
let pid = self.processes.create(CapabilityTable::new(), None).ok()?;
|
||||
if self.create_address_space(pid).is_err() {
|
||||
let _ = self.processes.destroy(pid);
|
||||
return None;
|
||||
}
|
||||
Some(pid)
|
||||
/// A process for boot code to load, its `limits` carved from `parent`.
|
||||
pub fn create_process_internal(
|
||||
&mut self,
|
||||
parent: ProcessId,
|
||||
limits: Limits,
|
||||
) -> Option<ProcessId> {
|
||||
self.new_process(Some(parent), limits).ok()
|
||||
}
|
||||
|
||||
pub fn spawn_thread_internal(&mut self, pid: ProcessId) -> Option<ThreadId> {
|
||||
@@ -237,7 +246,11 @@ impl<P: Platform> Kernel<P> {
|
||||
owner: ProcessId,
|
||||
) -> Result<types::id::EndpointId, KernelError> {
|
||||
self.processes.get(owner)?;
|
||||
self.endpoints.create(owner).ok_or(KernelError::NoFreeSlot)
|
||||
self.charge_object(owner, budget::ENDPOINTS)?;
|
||||
self.endpoints
|
||||
.create(owner)
|
||||
.ok_or(KernelError::NoFreeSlot)
|
||||
.inspect_err(|_| self.uncharge_object(owner, budget::ENDPOINTS))
|
||||
}
|
||||
|
||||
pub fn grant_capability(
|
||||
|
||||
@@ -91,22 +91,13 @@ impl<P: Platform> Kernel<P> {
|
||||
}
|
||||
|
||||
fn quota_mut(&mut self, pid: ProcessId) -> Result<&mut Quota, KernelError> {
|
||||
self.quotas
|
||||
.get_mut(pid.raw() as usize)
|
||||
.ok_or(KernelError::InvalidProcess(pid))
|
||||
self.budget_mut(pid).map(|b| &mut b.pages)
|
||||
}
|
||||
|
||||
pub fn page_usage(&self, pid: ProcessId) -> usize {
|
||||
self.quotas.get(pid.raw() as usize).map_or(0, Quota::used)
|
||||
}
|
||||
|
||||
pub fn set_page_limit(&mut self, pid: ProcessId, limit: usize) -> Result<(), KernelError> {
|
||||
self.processes.get(pid)?;
|
||||
if self.quota_mut(pid)?.set_limit(limit) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(KernelError::OutOfMemory)
|
||||
}
|
||||
self.budgets
|
||||
.get(pid.raw() as usize)
|
||||
.map_or(0, |b| b.pages.used())
|
||||
}
|
||||
|
||||
pub fn free_page_count(&self) -> usize {
|
||||
@@ -121,10 +112,11 @@ impl<P: Platform> Kernel<P> {
|
||||
pa: PhysicalAddress,
|
||||
perms: PagePerms,
|
||||
) -> Result<(), KernelError> {
|
||||
let quota = self
|
||||
.quotas
|
||||
let quota = &mut self
|
||||
.budgets
|
||||
.get_mut(pid.raw() as usize)
|
||||
.ok_or(KernelError::InvalidProcess(pid))?;
|
||||
.ok_or(KernelError::InvalidProcess(pid))?
|
||||
.pages;
|
||||
let mut source = ChargedFrames {
|
||||
frames: &mut self.frames,
|
||||
quota,
|
||||
@@ -136,11 +128,11 @@ impl<P: Platform> Kernel<P> {
|
||||
}
|
||||
|
||||
pub(crate) fn create_address_space(&mut self, pid: ProcessId) -> Result<(), KernelError> {
|
||||
let quota = self
|
||||
.quotas
|
||||
let quota = &mut self
|
||||
.budgets
|
||||
.get_mut(pid.raw() as usize)
|
||||
.ok_or(KernelError::InvalidProcess(pid))?;
|
||||
*quota = Quota::default();
|
||||
.ok_or(KernelError::InvalidProcess(pid))?
|
||||
.pages;
|
||||
let mut source = ChargedFrames {
|
||||
frames: &mut self.frames,
|
||||
quota,
|
||||
@@ -152,9 +144,6 @@ impl<P: Platform> Kernel<P> {
|
||||
.map_err(map_error);
|
||||
if created.is_err() {
|
||||
self.frames.free_owned_by(pid);
|
||||
if let Some(q) = self.quotas.get_mut(pid.raw() as usize) {
|
||||
*q = Quota::default();
|
||||
}
|
||||
}
|
||||
created
|
||||
}
|
||||
@@ -188,8 +177,8 @@ impl<P: Platform> Kernel<P> {
|
||||
if let Some((pa, _)) = self.user_frame(pid, va) {
|
||||
self.platform.unmap_page(pid, va);
|
||||
self.frames.free(pa);
|
||||
if let Some(q) = self.quotas.get_mut(pid.raw() as usize) {
|
||||
q.uncharge(1);
|
||||
if let Some(b) = self.budgets.get_mut(pid.raw() as usize) {
|
||||
b.pages.uncharge(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,9 +189,6 @@ impl<P: Platform> Kernel<P> {
|
||||
self.teardown_regions(pid);
|
||||
self.platform.destroy_address_space(pid);
|
||||
self.frames.free_owned_by(pid);
|
||||
if let Some(q) = self.quotas.get_mut(pid.raw() as usize) {
|
||||
*q = Quota::default();
|
||||
}
|
||||
if let Some(c) = self.heap_cursors.get_mut(pid.raw() as usize) {
|
||||
*c = 0;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,9 @@ impl<P: Platform> Kernel<P> {
|
||||
self.processes.get(owner)?;
|
||||
let id = self.regions.next_free_id().ok_or(KernelError::NoFreeSlot)?;
|
||||
let count = pages.value() as usize;
|
||||
self.charge_object(owner, crate::budget::REGIONS)?;
|
||||
if !self.quota_mut(owner)?.charge(count) {
|
||||
self.uncharge_object(owner, crate::budget::REGIONS);
|
||||
return Err(KernelError::OutOfMemory);
|
||||
}
|
||||
let Some(base) = self
|
||||
@@ -36,6 +38,7 @@ impl<P: Platform> Kernel<P> {
|
||||
.alloc_contiguous(count, |_| FrameUse::Region { region: id })
|
||||
else {
|
||||
self.quota_mut(owner)?.uncharge(count);
|
||||
self.uncharge_object(owner, crate::budget::REGIONS);
|
||||
return Err(KernelError::OutOfMemory);
|
||||
};
|
||||
for i in 0..count {
|
||||
@@ -76,9 +79,11 @@ impl<P: Platform> Kernel<P> {
|
||||
if overlaps {
|
||||
return Err(KernelError::AddressInUse);
|
||||
}
|
||||
self.charge_object(owner, crate::budget::REGIONS)?;
|
||||
self.regions
|
||||
.insert(Region::new(owner, RegionBacking::Device, base, pages))
|
||||
.ok_or(KernelError::NoFreeSlot)
|
||||
.inspect_err(|_| self.uncharge_object(owner, crate::budget::REGIONS))
|
||||
}
|
||||
|
||||
pub fn region_base(&self, id: RegionId) -> Option<PhysicalAddress> {
|
||||
|
||||
@@ -70,7 +70,13 @@ fn any_slot() -> CapSlot {
|
||||
fn small_world() -> Kernel<KaniPlatform> {
|
||||
let mut k = Kernel::new(KaniPlatform, PhysicalAddress::from_raw(0x8400_0000), 6);
|
||||
let (init, _) = k.boot(Priority::DEFAULT, TimeSlice::DEFAULT).unwrap();
|
||||
let b = k.create_process_internal().unwrap();
|
||||
let small = domain::budget::Limits {
|
||||
pages: 2,
|
||||
endpoints: 1,
|
||||
notifications: 1,
|
||||
regions: 1,
|
||||
};
|
||||
let b = k.create_process_internal(init, small).unwrap();
|
||||
k.spawn_thread_internal(b).unwrap();
|
||||
let ep = k.create_endpoint(init).unwrap();
|
||||
let n = k.create_notification(b).unwrap();
|
||||
|
||||
@@ -301,6 +301,7 @@ impl<P: Platform> Kernel<P> {
|
||||
Ok(slot) => SyscallOutcome::Complete(SyscallResult::Slot(slot)),
|
||||
Err(e) => {
|
||||
self.endpoints.destroy(id);
|
||||
self.uncharge_object(pid, crate::budget::ENDPOINTS);
|
||||
SyscallOutcome::Complete(SyscallResult::Error(e))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,9 +38,11 @@ impl<P: Platform> Kernel<P> {
|
||||
|
||||
pub fn create_notification(&mut self, owner: ProcessId) -> Result<NotificationId, KernelError> {
|
||||
self.processes.get(owner)?;
|
||||
self.charge_object(owner, crate::budget::NOTIFICATIONS)?;
|
||||
self.notifications
|
||||
.create(owner)
|
||||
.ok_or(KernelError::NoFreeSlot)
|
||||
.inspect_err(|_| self.uncharge_object(owner, crate::budget::NOTIFICATIONS))
|
||||
}
|
||||
|
||||
pub(crate) fn create_notification_cap(
|
||||
@@ -51,6 +53,7 @@ impl<P: Platform> Kernel<P> {
|
||||
self.grant_cap(pid, Resource::Notification(id), rw_delegate())
|
||||
.inspect_err(|_| {
|
||||
self.notifications.destroy(id);
|
||||
self.uncharge_object(pid, crate::budget::NOTIFICATIONS);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ impl<P: Platform> Kernel<P> {
|
||||
caller_pid: types::id::ProcessId,
|
||||
create_cap: types::bounded::CapSlot,
|
||||
_image: types::address::DataRef,
|
||||
limits: domain::budget::Limits,
|
||||
) -> SyscallOutcome {
|
||||
if let Err(e) = self.check_cap(
|
||||
caller_pid,
|
||||
@@ -50,19 +51,11 @@ impl<P: Platform> Kernel<P> {
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(e));
|
||||
}
|
||||
|
||||
let new_pid = match self
|
||||
.processes
|
||||
.create(domain::capability::CapabilityTable::new(), None)
|
||||
{
|
||||
let new_pid = match self.new_process(Some(caller_pid), limits) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||
};
|
||||
|
||||
if let Err(e) = self.create_address_space(new_pid) {
|
||||
let _ = self.processes.destroy(new_pid);
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(e));
|
||||
}
|
||||
|
||||
match self.grant_cap(
|
||||
caller_pid,
|
||||
domain::resource::Resource::Process(new_pid),
|
||||
@@ -75,9 +68,7 @@ impl<P: Platform> Kernel<P> {
|
||||
) {
|
||||
Ok(slot) => SyscallOutcome::Complete(SyscallResult::Slot(slot)),
|
||||
Err(e) => {
|
||||
// Rollback: destroy orphaned process
|
||||
self.teardown_memory(new_pid);
|
||||
let _ = self.processes.destroy(new_pid);
|
||||
self.destroy_process(new_pid);
|
||||
SyscallOutcome::Complete(SyscallResult::Error(e))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,9 +83,11 @@ impl<P: Platform> Kernel<P> {
|
||||
Syscall::SpawnThread { entry, stack, arg } => {
|
||||
self.handle_spawn_thread(pid, entry, stack, arg)
|
||||
}
|
||||
Syscall::CreateProcess { create_cap, image } => {
|
||||
self.handle_create_process(pid, create_cap, image)
|
||||
}
|
||||
Syscall::CreateProcess {
|
||||
create_cap,
|
||||
image,
|
||||
limits,
|
||||
} => self.handle_create_process(pid, create_cap, image, limits),
|
||||
Syscall::Kill { process } => self.handle_kill(pid, process),
|
||||
Syscall::Revoke { slot } => self.handle_revoke(pid, slot),
|
||||
Syscall::DropCap { slot } => self.handle_drop_cap(pid, slot),
|
||||
|
||||
@@ -28,6 +28,7 @@ impl<P: Platform> Kernel<P> {
|
||||
self.destroy_notifications_owned_by(pid);
|
||||
self.irqs.clear_owned_by(pid);
|
||||
self.teardown_memory(pid);
|
||||
self.release_budget(pid);
|
||||
|
||||
let _ = self.processes.destroy(pid);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ fn crowded(
|
||||
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 {
|
||||
let Some(pid) = k.create_process_for_test() else {
|
||||
break;
|
||||
};
|
||||
while k
|
||||
@@ -173,7 +173,7 @@ fn bench_create_spawn_kill() {
|
||||
let rounds = 2_000;
|
||||
let start = Instant::now();
|
||||
for _ in 0..rounds {
|
||||
let pid = k.create_process_internal().unwrap();
|
||||
let pid = k.create_process_for_test().unwrap();
|
||||
k.spawn_thread_internal(pid).unwrap();
|
||||
k.destroy_process(pid);
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ fn halt_needs_execute_on_the_cap() {
|
||||
#[should_panic(expected = "halt")]
|
||||
fn any_holder_of_the_cap_may_halt_not_just_init() {
|
||||
let (mut k, init) = booted();
|
||||
let other = k.create_process_internal().unwrap();
|
||||
let other = k.create_process_for_test().unwrap();
|
||||
let tid = k.spawn_thread_for_test(other, Priority::DEFAULT, TimeSlice::DEFAULT);
|
||||
k.grant_cap_for_test(
|
||||
other,
|
||||
@@ -207,7 +207,7 @@ fn granting_needs_a_registered_board() {
|
||||
#[test]
|
||||
fn granting_needs_the_regions_to_be_inits() {
|
||||
let (mut k, init) = kernel_with_process();
|
||||
let other = k.create_process_internal().unwrap();
|
||||
let other = k.create_process_for_test().unwrap();
|
||||
k.register_board(other, &board()).unwrap();
|
||||
assert!(matches!(
|
||||
k.grant_boot_authority(init, &board(), &mut [None; 2]),
|
||||
|
||||
152
crates/kernel/src/tests/budget.rs
Normal file
152
crates/kernel/src/tests/budget.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
//! Budgets: carved from the creator, enforced, and returned on death.
|
||||
|
||||
use super::helpers::CHILD_LIMITS;
|
||||
use super::*;
|
||||
use crate::{SyscallOutcome, SyscallResult};
|
||||
use domain::budget::Limits;
|
||||
use domain::error::KernelError;
|
||||
use domain::syscall::Syscall;
|
||||
use types::address::DataRef;
|
||||
use types::bounded::{CapSlot, PageCount};
|
||||
|
||||
fn create(k: &mut Kernel<MockPlatform>, limits: Limits) -> SyscallResult {
|
||||
match k.handle_syscall(Syscall::CreateProcess {
|
||||
create_cap: CapSlot::new(0).unwrap(),
|
||||
image: DataRef::empty(),
|
||||
limits,
|
||||
}) {
|
||||
SyscallOutcome::Complete(r) => r,
|
||||
_ => panic!("create process blocked"),
|
||||
}
|
||||
}
|
||||
|
||||
fn minus(a: Limits, b: Limits) -> Limits {
|
||||
Limits {
|
||||
pages: a.pages - b.pages,
|
||||
endpoints: a.endpoints - b.endpoints,
|
||||
notifications: a.notifications - b.notifications,
|
||||
regions: a.regions - b.regions,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn init_starts_with_the_whole_pool() {
|
||||
let (k, init) = kernel_with_process();
|
||||
let l = k.limits_of(init).unwrap();
|
||||
assert_eq!(l.pages, k.free_page_count() + k.page_usage(init));
|
||||
assert_eq!(l.endpoints, domain::endpoint::MAX_ENDPOINTS);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_child_is_carved_from_its_creator() {
|
||||
let (mut k, init) = kernel_with_process();
|
||||
let before = k.limits_of(init).unwrap();
|
||||
let SyscallResult::Slot(_) = create(&mut k, CHILD_LIMITS) else {
|
||||
panic!("create failed");
|
||||
};
|
||||
assert_eq!(k.limits_of(init).unwrap(), minus(before, CHILD_LIMITS));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_child_gets_no_more_than_its_creator_has_spare() {
|
||||
let (mut k, init) = kernel_with_process();
|
||||
let before = k.limits_of(init).unwrap();
|
||||
// The creator's own pages are in use, so its whole limit is not spare.
|
||||
let greedy = Limits {
|
||||
pages: before.pages,
|
||||
..CHILD_LIMITS
|
||||
};
|
||||
assert!(matches!(
|
||||
create(&mut k, greedy),
|
||||
SyscallResult::Error(KernelError::OutOfMemory)
|
||||
));
|
||||
let greedy = Limits {
|
||||
endpoints: before.endpoints + 1,
|
||||
..CHILD_LIMITS
|
||||
};
|
||||
assert!(matches!(
|
||||
create(&mut k, greedy),
|
||||
SyscallResult::Error(KernelError::OutOfMemory)
|
||||
));
|
||||
assert_eq!(k.limits_of(init).unwrap(), before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kernel_objects_count_against_the_owner() {
|
||||
let (mut k, init) = kernel_with_process();
|
||||
let one = Limits {
|
||||
pages: 8,
|
||||
endpoints: 1,
|
||||
notifications: 1,
|
||||
regions: 1,
|
||||
};
|
||||
let child = k.create_process_internal(init, one).unwrap();
|
||||
assert!(k.create_endpoint(child).is_ok());
|
||||
assert!(matches!(
|
||||
k.create_endpoint(child),
|
||||
Err(KernelError::NoFreeSlot)
|
||||
));
|
||||
assert!(k.create_notification(child).is_ok());
|
||||
assert!(matches!(
|
||||
k.create_notification(child),
|
||||
Err(KernelError::NoFreeSlot)
|
||||
));
|
||||
let page = PageCount::new(1).unwrap();
|
||||
assert!(k.create_pool_region(child, page).is_ok());
|
||||
assert!(matches!(
|
||||
k.create_pool_region(child, page),
|
||||
Err(KernelError::NoFreeSlot)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_failed_region_leaves_no_charge_behind() {
|
||||
let (mut k, init) = kernel_with_process();
|
||||
let one = Limits {
|
||||
pages: 2,
|
||||
endpoints: 0,
|
||||
notifications: 0,
|
||||
regions: 1,
|
||||
};
|
||||
let child = k.create_process_internal(init, one).unwrap();
|
||||
// More pages than the child may hold: the region slot stays free.
|
||||
assert!(matches!(
|
||||
k.create_pool_region(child, PageCount::new(8).unwrap()),
|
||||
Err(KernelError::OutOfMemory)
|
||||
));
|
||||
assert!(
|
||||
k.create_pool_region(child, PageCount::new(1).unwrap())
|
||||
.is_ok()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn death_returns_the_budget_to_the_parent() {
|
||||
let (mut k, init) = kernel_with_process();
|
||||
let before = k.limits_of(init).unwrap();
|
||||
let child = k.create_process_internal(init, CHILD_LIMITS).unwrap();
|
||||
k.create_endpoint(child).unwrap();
|
||||
k.destroy_process(child);
|
||||
assert_eq!(k.limits_of(init).unwrap(), before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_orphan_returns_its_budget_to_the_grandparent() {
|
||||
let (mut k, init) = kernel_with_process();
|
||||
let before = k.limits_of(init).unwrap();
|
||||
let a = k.create_process_internal(init, CHILD_LIMITS).unwrap();
|
||||
let small = Limits {
|
||||
pages: 4,
|
||||
endpoints: 1,
|
||||
notifications: 1,
|
||||
regions: 1,
|
||||
};
|
||||
let b = k.create_process_internal(a, small).unwrap();
|
||||
k.destroy_process(a);
|
||||
assert_eq!(k.limits_of(init).unwrap(), minus(before, small));
|
||||
// a's slot may be reused; b's budget must still find its way home.
|
||||
let c = k.create_process_internal(init, CHILD_LIMITS).unwrap();
|
||||
k.destroy_process(b);
|
||||
k.destroy_process(c);
|
||||
assert_eq!(k.limits_of(init).unwrap(), before);
|
||||
}
|
||||
@@ -28,6 +28,7 @@ fn delegate_with_valid_cap_succeeds() {
|
||||
let target_slot = match k.handle_syscall(Syscall::CreateProcess {
|
||||
create_cap: CapSlot::new(0).unwrap(),
|
||||
image: DataRef::empty(),
|
||||
limits: super::helpers::CHILD_LIMITS,
|
||||
}) {
|
||||
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
||||
_ => panic!("expected slot"),
|
||||
|
||||
@@ -163,6 +163,7 @@ impl World {
|
||||
Syscall::CreateProcess {
|
||||
create_cap: CapSlot::new(0).unwrap(),
|
||||
image: DataRef::empty(),
|
||||
limits: super::helpers::CHILD_LIMITS,
|
||||
},
|
||||
);
|
||||
self.pids[i] = self.k.process_of_cap_for_test(cap);
|
||||
|
||||
@@ -24,7 +24,32 @@ pub fn kernel_with_process() -> (Kernel<MockPlatform>, ProcessId) {
|
||||
(kernel, pid)
|
||||
}
|
||||
|
||||
/// What a test's child processes get: small, so several fit in the pool.
|
||||
pub const CHILD_LIMITS: domain::budget::Limits = domain::budget::Limits {
|
||||
pages: 32,
|
||||
endpoints: 4,
|
||||
notifications: 4,
|
||||
regions: 2,
|
||||
};
|
||||
|
||||
impl Kernel<MockPlatform> {
|
||||
/// Shrinks `pid`'s page limit to `limit` (never below its usage).
|
||||
pub fn cap_pages_for_test(&mut self, pid: ProcessId, limit: usize) {
|
||||
let pages = &mut self.budgets[pid.raw() as usize].pages;
|
||||
assert!(pages.carve(pages.limit() - limit));
|
||||
}
|
||||
|
||||
/// A process outside any budget tree, as tests used to get by default.
|
||||
pub fn create_process_for_test(&mut self) -> Option<ProcessId> {
|
||||
let limits = domain::budget::Limits {
|
||||
pages: 1024,
|
||||
endpoints: domain::endpoint::MAX_ENDPOINTS,
|
||||
notifications: domain::notification::MAX_NOTIFICATIONS,
|
||||
regions: domain::memory::MAX_REGIONS,
|
||||
};
|
||||
self.new_process(None, limits).ok()
|
||||
}
|
||||
|
||||
pub fn spawn_thread_for_test(
|
||||
&mut self,
|
||||
pid: ProcessId,
|
||||
|
||||
@@ -66,6 +66,7 @@ impl World {
|
||||
caps[i] = match k.handle_syscall(Syscall::CreateProcess {
|
||||
create_cap: CapSlot::new(0).unwrap(),
|
||||
image: DataRef::empty(),
|
||||
limits: super::helpers::CHILD_LIMITS,
|
||||
}) {
|
||||
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
||||
_ => panic!("create process"),
|
||||
@@ -164,6 +165,7 @@ impl World {
|
||||
let fresh = match self.k.handle_syscall(Syscall::CreateProcess {
|
||||
create_cap: CapSlot::new(0).unwrap(),
|
||||
image: DataRef::empty(),
|
||||
limits: super::helpers::CHILD_LIMITS,
|
||||
}) {
|
||||
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
||||
_ => panic!("re-create process"),
|
||||
|
||||
@@ -42,6 +42,7 @@ impl Ipc {
|
||||
match k.handle_syscall(Syscall::CreateProcess {
|
||||
create_cap: CapSlot::new(0).unwrap(),
|
||||
image: DataRef::empty(),
|
||||
limits: super::helpers::CHILD_LIMITS,
|
||||
}) {
|
||||
SyscallOutcome::Complete(SyscallResult::Slot(_)) => {}
|
||||
_ => panic!("create process"),
|
||||
@@ -487,7 +488,7 @@ fn a_reply_never_reaches_a_thread_that_reused_the_callers_id() {
|
||||
let mut t = Ipc::new();
|
||||
let (ta, tb, b, b_recv) = (t.ta, t.tb, t.b, t.b_recv);
|
||||
// A second client process C calls, then dies while B holds the reply.
|
||||
let c = t.k.create_process_internal().unwrap();
|
||||
let c = t.k.create_process_for_test().unwrap();
|
||||
let tc =
|
||||
t.k.spawn_thread_for_test(c, Priority::DEFAULT, TimeSlice::DEFAULT);
|
||||
let c_send =
|
||||
|
||||
@@ -64,8 +64,8 @@ impl World {
|
||||
fn new() -> Self {
|
||||
let (mut k, p0) = kernel_with_process();
|
||||
let t0 = k.current_thread().unwrap();
|
||||
let p1 = k.create_process_internal().unwrap();
|
||||
let p2 = k.create_process_internal().unwrap();
|
||||
let p1 = k.create_process_for_test().unwrap();
|
||||
let p2 = k.create_process_for_test().unwrap();
|
||||
// Endpoints and the notification belong to init, which never dies.
|
||||
let eps = [
|
||||
k.create_endpoint(p0).unwrap(),
|
||||
@@ -168,7 +168,7 @@ impl World {
|
||||
/// Kills process `pi` and brings it back with fresh threads and caps.
|
||||
fn reincarnate(&mut self, pi: usize) {
|
||||
self.k.destroy_process(self.procs[pi].pid);
|
||||
self.procs[pi].pid = self.k.create_process_internal().unwrap();
|
||||
self.procs[pi].pid = self.k.create_process_for_test().unwrap();
|
||||
let t0 = self.procs[0].threads[0];
|
||||
Self::equip(
|
||||
&mut self.k,
|
||||
|
||||
@@ -61,6 +61,7 @@ fn two_processes() -> (
|
||||
let cap_b = match k.handle_syscall(Syscall::CreateProcess {
|
||||
create_cap: CapSlot::new(0).unwrap(),
|
||||
image: DataRef::empty(),
|
||||
limits: super::helpers::CHILD_LIMITS,
|
||||
}) {
|
||||
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
||||
_ => panic!("create process"),
|
||||
@@ -219,7 +220,7 @@ fn page_tables_count_against_the_process_quota() {
|
||||
let (mut k, pid) = kernel_with_process();
|
||||
let used = k.page_usage(pid);
|
||||
// Room for exactly one data page plus the leaf table it needs.
|
||||
k.set_page_limit(pid, used + 2).unwrap();
|
||||
k.cap_pages_for_test(pid, used + 2);
|
||||
assert!(request(&mut k, 1).is_some());
|
||||
assert!(request(&mut k, 1).is_none(), "quota should be exhausted");
|
||||
}
|
||||
@@ -231,6 +232,7 @@ fn killing_a_process_returns_its_page_tables_too() {
|
||||
let cap_b = match k.handle_syscall(Syscall::CreateProcess {
|
||||
create_cap: CapSlot::new(0).unwrap(),
|
||||
image: DataRef::empty(),
|
||||
limits: super::helpers::CHILD_LIMITS,
|
||||
}) {
|
||||
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
||||
_ => panic!("create process"),
|
||||
@@ -260,7 +262,7 @@ fn map_page_fails_cleanly_when_target_quota_is_full() {
|
||||
let (mut k, pid_a, _, pid_b, _, cap_b) = two_processes();
|
||||
let src = request(&mut k, 1).unwrap();
|
||||
let used = k.page_usage(pid_b);
|
||||
k.set_page_limit(pid_b, used).unwrap();
|
||||
k.cap_pages_for_test(pid_b, used);
|
||||
assert!(!map_page(&mut k, cap_b, src, 0x10000));
|
||||
// the page stays with the caller
|
||||
assert!(k.platform_mut().mapping(pid_a, src).is_some());
|
||||
|
||||
@@ -47,6 +47,7 @@ fn create_process_with_cap_succeeds() {
|
||||
let outcome = k.handle_syscall(Syscall::CreateProcess {
|
||||
create_cap: CapSlot::new(0).unwrap(),
|
||||
image: DataRef::empty(),
|
||||
limits: super::helpers::CHILD_LIMITS,
|
||||
});
|
||||
assert!(matches!(
|
||||
outcome,
|
||||
@@ -61,6 +62,7 @@ fn create_process_without_cap_fails() {
|
||||
let outcome = k.handle_syscall(Syscall::CreateProcess {
|
||||
create_cap: CapSlot::new(5).unwrap(),
|
||||
image: DataRef::empty(),
|
||||
limits: super::helpers::CHILD_LIMITS,
|
||||
});
|
||||
assert!(matches!(
|
||||
outcome,
|
||||
@@ -75,6 +77,7 @@ fn kill_removes_process() {
|
||||
let slot = match k.handle_syscall(Syscall::CreateProcess {
|
||||
create_cap: CapSlot::new(0).unwrap(),
|
||||
image: DataRef::empty(),
|
||||
limits: super::helpers::CHILD_LIMITS,
|
||||
}) {
|
||||
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
||||
_ => panic!("expected slot"),
|
||||
|
||||
@@ -16,6 +16,7 @@ fn create_process(k: &mut Kernel<MockPlatform>) -> CapSlot {
|
||||
match k.handle_syscall(Syscall::CreateProcess {
|
||||
create_cap: CapSlot::new(0).unwrap(),
|
||||
image: DataRef::empty(),
|
||||
limits: super::helpers::CHILD_LIMITS,
|
||||
}) {
|
||||
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
||||
_ => panic!("create process"),
|
||||
|
||||
@@ -70,7 +70,7 @@ 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();
|
||||
let b = k.create_process_for_test().unwrap();
|
||||
k.map_fresh_range(
|
||||
b,
|
||||
VirtualAddress::from_raw(0x50000),
|
||||
|
||||
@@ -4,6 +4,7 @@ mod mock_platform;
|
||||
mod abi;
|
||||
mod bench;
|
||||
mod board;
|
||||
mod budget;
|
||||
mod capability;
|
||||
mod captree_model;
|
||||
mod exit;
|
||||
|
||||
@@ -59,6 +59,7 @@ impl Tree {
|
||||
let create = || Syscall::CreateProcess {
|
||||
create_cap: CapSlot::new(0).unwrap(),
|
||||
image: DataRef::empty(),
|
||||
limits: super::helpers::CHILD_LIMITS,
|
||||
};
|
||||
let a_to_b = slot_of(k.handle_syscall(create()));
|
||||
let a_to_c = slot_of(k.handle_syscall(create()));
|
||||
|
||||
@@ -96,7 +96,7 @@ fn a_busy_core_is_not_kicked_for_an_equal_priority_thread() {
|
||||
#[test]
|
||||
fn killing_a_thread_running_on_another_core_stops_that_core() {
|
||||
let (mut k, init) = smp(2);
|
||||
let child = k.create_process_internal().unwrap();
|
||||
let child = k.create_process_for_test().unwrap();
|
||||
let t = k.spawn_thread_internal(child).unwrap();
|
||||
on(&mut k, core(1));
|
||||
k.platform_mut().take_ipi(core(1));
|
||||
|
||||
@@ -319,7 +319,7 @@ fn new_round(k: &mut Kernel<MockPlatform>, init: ProcessId, init_thread: ThreadI
|
||||
let _ = k.delete_cap(init, slot);
|
||||
}
|
||||
}
|
||||
let Some(hub) = k.create_process_internal() else {
|
||||
let Some(hub) = k.create_process_for_test() else {
|
||||
if std::env::var("STORM_DEBUG").is_ok() {
|
||||
std::eprintln!("no hub: free pages {}", k.free_page_count());
|
||||
}
|
||||
@@ -346,6 +346,7 @@ fn new_round(k: &mut Kernel<MockPlatform>, init: ProcessId, init_thread: ThreadI
|
||||
k.handle_syscall(domain::syscall::Syscall::CreateProcess {
|
||||
create_cap: CapSlot::new(0).unwrap(),
|
||||
image: DataRef::empty(),
|
||||
limits: super::helpers::CHILD_LIMITS,
|
||||
})
|
||||
{
|
||||
let pid = k.process_of_cap_for_test(cap);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use super::{call0, raw, slot_of};
|
||||
use types::address::{EntryPoint, StackPointer};
|
||||
use types::bounded::CapSlot;
|
||||
use types::budget::Limits;
|
||||
use types::id::ProcessId;
|
||||
use types::syscall_abi::*;
|
||||
use types::time::Ticks;
|
||||
@@ -38,10 +39,18 @@ pub fn get_process_id() -> ProcessId {
|
||||
ProcessId::from_raw(call0(SYS_GET_PROCESS_ID).map_or(0, |r| r[1]) as u16)
|
||||
}
|
||||
|
||||
pub fn create_process(create_cap: CapSlot) -> Option<CapSlot> {
|
||||
/// The new process's `limits` come out of this process's own budget.
|
||||
pub fn create_process(create_cap: CapSlot, limits: Limits) -> Option<CapSlot> {
|
||||
slot_of(raw(
|
||||
SYS_CREATE_PROCESS,
|
||||
[create_cap.value() as usize, 0, 0, 0, 0, 0],
|
||||
[
|
||||
create_cap.value() as usize,
|
||||
limits.pages,
|
||||
limits.endpoints,
|
||||
limits.notifications,
|
||||
limits.regions,
|
||||
0,
|
||||
],
|
||||
))
|
||||
.ok()
|
||||
}
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
use server_framework::Server;
|
||||
use types::bounded::CapSlot;
|
||||
use types::budget::Limits;
|
||||
|
||||
use crate::protocol::{AppId, SessionRequest, SessionResponse};
|
||||
|
||||
const MAX_APPS: usize = 16;
|
||||
|
||||
/// What each launched app may hold, out of the session server's budget.
|
||||
const APP_LIMITS: Limits = Limits {
|
||||
pages: 256,
|
||||
endpoints: 2,
|
||||
notifications: 2,
|
||||
regions: 2,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct AppEntry {
|
||||
pub name: &'static str,
|
||||
@@ -77,7 +86,10 @@ impl SessionServer {
|
||||
if self.running_mut(app).is_none_or(|r| r.is_some()) {
|
||||
return SessionResponse::Error;
|
||||
}
|
||||
let Some(process) = self.create_cap.and_then(libkos::syscall::create_process) else {
|
||||
let Some(process) = self
|
||||
.create_cap
|
||||
.and_then(|c| libkos::syscall::create_process(c, APP_LIMITS))
|
||||
else {
|
||||
return SessionResponse::Error;
|
||||
};
|
||||
if let Some(r) = self.running_mut(app) {
|
||||
|
||||
10
crates/types/src/budget.rs
Normal file
10
crates/types/src/budget.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
//! How much a process may hold, as a creator asks for it in `CreateProcess`
|
||||
//! (`a1..a4`). The kernel carves it from the creator's own budget.
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct Limits {
|
||||
pub pages: usize,
|
||||
pub endpoints: usize,
|
||||
pub notifications: usize,
|
||||
pub regions: usize,
|
||||
}
|
||||
@@ -4,6 +4,7 @@ pub mod address;
|
||||
pub mod badge;
|
||||
pub mod bootinfo;
|
||||
pub mod bounded;
|
||||
pub mod budget;
|
||||
pub mod display;
|
||||
pub mod generation;
|
||||
pub mod id;
|
||||
|
||||
Reference in New Issue
Block a user