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::hw::virtio_blk::VirtioBlk;
|
||||||
use crate::{boot_fs, mmu};
|
use crate::{boot_fs, mmu};
|
||||||
use types::address::VirtualAddress;
|
use types::address::VirtualAddress;
|
||||||
|
use types::budget::Limits;
|
||||||
use types::id::{ProcessId, ThreadId};
|
use types::id::{ProcessId, ThreadId};
|
||||||
use types::mapping::PagePerms;
|
use types::mapping::PagePerms;
|
||||||
|
|
||||||
@@ -52,14 +53,38 @@ pub fn load_init(uart: &Uart, blk: &mut VirtioBlk) -> LoadedProcess {
|
|||||||
LoadedProcess { pid, tid }
|
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(
|
pub fn load_server(
|
||||||
uart: &Uart,
|
uart: &Uart,
|
||||||
blk: &mut VirtioBlk,
|
blk: &mut VirtioBlk,
|
||||||
|
init: ProcessId,
|
||||||
filename: &[u8],
|
filename: &[u8],
|
||||||
label: &str,
|
label: &str,
|
||||||
|
limits: Limits,
|
||||||
) -> LoadedProcess {
|
) -> LoadedProcess {
|
||||||
let pid = kern()
|
let pid = kern()
|
||||||
.create_process_internal()
|
.create_process_internal(init, limits)
|
||||||
.unwrap_or_else(|| crate::halt(uart, "process create failed"));
|
.unwrap_or_else(|| crate::halt(uart, "process create failed"));
|
||||||
let entry = load_elf(uart, blk, pid, filename);
|
let entry = load_elf(uart, blk, pid, filename);
|
||||||
let tid = kern()
|
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 crate::hw::uart::Uart;
|
||||||
use types::address::VirtualAddress;
|
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::id::ProcessId;
|
||||||
use types::mapping::PagePerms;
|
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> {
|
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
||||||
unsafe { &mut *crate::kernel_ptr() }
|
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
|
/// Tells init, in a read-only page at BOOT_INFO_VA, which of its slots hold
|
||||||
/// each board device's capabilities.
|
/// each board device's capabilities.
|
||||||
pub(crate) fn boot_info(
|
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"));
|
boot::hardware::probe_block_device(&uart).unwrap_or_else(|| halt(&uart, "no block device"));
|
||||||
|
|
||||||
let init = boot::loader::load_init(&uart, &mut blk);
|
let init = boot::loader::load_init(&uart, &mut blk);
|
||||||
let storage = boot::loader::load_server(&uart, &mut blk, b"storage.elf", "storage");
|
use boot::loader::{DISPLAY, INPUT, STORAGE, load_server};
|
||||||
let display = boot::loader::load_server(&uart, &mut blk, b"display.elf", "display");
|
let storage = load_server(
|
||||||
let input = boot::loader::load_server(&uart, &mut blk, b"input.elf", "input");
|
&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"));
|
let board = board::devices().unwrap_or_else(|| halt(&uart, "bad board table"));
|
||||||
kern_mut()
|
kern_mut()
|
||||||
.register_board(init.pid, &board)
|
.register_board(init.pid, &board)
|
||||||
.unwrap_or_else(|_| halt(&uart, "board registration failed"));
|
.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);
|
boot::caps::grant_all(&uart, init.pid, storage.pid, display.pid, input.pid);
|
||||||
// Last, so the servers' fixed slots stay where they expect them.
|
// Last, so the servers' fixed slots stay where they expect them.
|
||||||
boot::caps::grant_console(&uart, &[storage, display, input]);
|
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)]
|
#[cfg(kani)]
|
||||||
pub mod proofs;
|
pub mod proofs;
|
||||||
|
|
||||||
|
pub mod budget;
|
||||||
pub mod capability;
|
pub mod capability;
|
||||||
pub mod endpoint;
|
pub mod endpoint;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/// Per-process page budget. Every frame charged to a process (user pages
|
/// A count against a limit: one of a process's budgets (see `budget`).
|
||||||
/// and its page tables) counts against it.
|
/// For pages, every frame charged to a process (user pages and its page
|
||||||
|
/// tables) counts against it.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct Quota {
|
pub struct Quota {
|
||||||
used: usize,
|
used: usize,
|
||||||
@@ -7,8 +8,6 @@ pub struct Quota {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Quota {
|
impl Quota {
|
||||||
pub const DEFAULT_LIMIT: usize = 1024;
|
|
||||||
|
|
||||||
pub const fn new(limit: usize) -> Self {
|
pub const fn new(limit: usize) -> Self {
|
||||||
Self { used: 0, limit }
|
Self { used: 0, limit }
|
||||||
}
|
}
|
||||||
@@ -41,17 +40,19 @@ impl Quota {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_limit(&mut self, limit: usize) -> bool {
|
/// Gives up `amount` of the unused limit, for a child to hold.
|
||||||
if limit < self.used {
|
pub fn carve(&mut self, amount: usize) -> bool {
|
||||||
return false;
|
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 {
|
/// Takes back a limit carved out earlier.
|
||||||
fn default() -> Self {
|
pub fn refund(&mut self, amount: usize) {
|
||||||
Self::new(Self::DEFAULT_LIMIT)
|
self.limit = self.limit.saturating_add(amount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,10 +28,13 @@ fn uncharge_cannot_underflow() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn limit_can_be_raised_but_not_below_usage() {
|
fn only_the_unused_limit_can_be_carved() {
|
||||||
let mut q = Quota::new(4);
|
let mut q = Quota::new(4);
|
||||||
q.charge(3);
|
q.charge(3);
|
||||||
assert!(!q.set_limit(2));
|
assert!(!q.carve(2));
|
||||||
assert!(q.set_limit(10));
|
assert_eq!(q.limit(), 4);
|
||||||
assert!(q.charge(7));
|
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 capability;
|
||||||
mod delegation;
|
mod delegation;
|
||||||
mod thread;
|
mod thread;
|
||||||
|
|||||||
@@ -62,9 +62,11 @@ pub enum Syscall {
|
|||||||
AcknowledgeInterrupt {
|
AcknowledgeInterrupt {
|
||||||
irq: CapSlot,
|
irq: CapSlot,
|
||||||
},
|
},
|
||||||
|
/// The new process's `limits` are carved from the caller's budget.
|
||||||
CreateProcess {
|
CreateProcess {
|
||||||
create_cap: CapSlot,
|
create_cap: CapSlot,
|
||||||
image: DataRef,
|
image: DataRef,
|
||||||
|
limits: crate::budget::Limits,
|
||||||
},
|
},
|
||||||
Kill {
|
Kill {
|
||||||
process: CapSlot,
|
process: CapSlot,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ mod selftest;
|
|||||||
|
|
||||||
use types::address::{EntryPoint, StackPointer, VirtualAddress};
|
use types::address::{EntryPoint, StackPointer, VirtualAddress};
|
||||||
use types::bounded::{CapSlot, PageCount};
|
use types::bounded::{CapSlot, PageCount};
|
||||||
|
use types::budget::Limits;
|
||||||
use types::permission::{Permission, PermissionSet};
|
use types::permission::{Permission, PermissionSet};
|
||||||
use libkos::fs::Storage;
|
use libkos::fs::Storage;
|
||||||
use libkos::syscall;
|
use libkos::syscall;
|
||||||
@@ -42,7 +43,12 @@ pub(crate) struct LoadedProcess {
|
|||||||
pub(crate) entry_point: EntryPoint,
|
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 file_size = read_file(storage, scratch, filename)?;
|
||||||
let elf_data = &scratch[..file_size];
|
let elf_data = &scratch[..file_size];
|
||||||
let elf_info = match elf::parse(elf_data) {
|
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,
|
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() {
|
for segment in elf_info.segments() {
|
||||||
let page_start = segment.vaddr & !0xFFF;
|
let page_start = segment.vaddr & !0xFFF;
|
||||||
@@ -102,14 +108,25 @@ pub(crate) fn load_binary(storage: &mut Storage, scratch: &mut [u8], filename: &
|
|||||||
struct ServerInfo {
|
struct ServerInfo {
|
||||||
filename: &'static [u8],
|
filename: &'static [u8],
|
||||||
name: &'static str,
|
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] = &[
|
const SERVERS: &[ServerInfo] = &[
|
||||||
ServerInfo { filename: b"log.elf", name: "log" },
|
ServerInfo { filename: b"log.elf", name: "log", limits: SMALL },
|
||||||
ServerInfo { filename: b"permis.elf", name: "permis" },
|
ServerInfo { filename: b"permis.elf", name: "permis", limits: SMALL },
|
||||||
ServerInfo { filename: b"session.elf", name: "session" },
|
ServerInfo { filename: b"session.elf", name: "session", limits: SMALL },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const SNAKE: Limits = Limits { pages: 128, ..SMALL };
|
||||||
|
|
||||||
pub fn run() -> ! {
|
pub fn run() -> ! {
|
||||||
libkos::start::set_console(devices::console());
|
libkos::start::set_console(devices::console());
|
||||||
libkos::println!("init: started (pid={:#x})", syscall::get_process_id().raw());
|
libkos::println!("init: started (pid={:#x})", syscall::get_process_id().raw());
|
||||||
@@ -142,7 +159,7 @@ pub fn run() -> ! {
|
|||||||
for server in SERVERS {
|
for server in SERVERS {
|
||||||
libkos::print!("init: loading {}... ", server.name);
|
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,
|
Some(l) => l,
|
||||||
None => {
|
None => {
|
||||||
libkos::println!("SKIP");
|
libkos::println!("SKIP");
|
||||||
@@ -166,7 +183,7 @@ pub fn run() -> ! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
libkos::print!("init: loading snake... ");
|
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
|
// A client only calls: Execute, never Read, so it cannot pose as
|
||||||
// the server and take other clients' requests.
|
// the server and take other clients' requests.
|
||||||
let client = PermissionSet::empty().with(Permission::Execute);
|
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 {
|
for i in 0..=THREADS {
|
||||||
word(i).store(0, Ordering::SeqCst);
|
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()
|
let rw = PermissionSet::empty()
|
||||||
.with(Permission::Read)
|
.with(Permission::Read)
|
||||||
.with(Permission::Write)
|
.with(Permission::Write)
|
||||||
|
|||||||
@@ -94,6 +94,12 @@ pub fn decode(nr: usize, a: &[usize; 6]) -> Option<Syscall> {
|
|||||||
SYS_CREATE_PROCESS => Syscall::CreateProcess {
|
SYS_CREATE_PROCESS => Syscall::CreateProcess {
|
||||||
create_cap: slot(a[0])?,
|
create_cap: slot(a[0])?,
|
||||||
image: DataRef::empty(),
|
image: DataRef::empty(),
|
||||||
|
limits: domain::budget::Limits {
|
||||||
|
pages: a[1],
|
||||||
|
endpoints: a[2],
|
||||||
|
notifications: a[3],
|
||||||
|
regions: a[4],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
SYS_KILL => Syscall::Kill {
|
SYS_KILL => Syscall::Kill {
|
||||||
process: slot(a[0])?,
|
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;
|
pub mod abi;
|
||||||
mod board;
|
mod board;
|
||||||
|
mod budget;
|
||||||
mod captree;
|
mod captree;
|
||||||
pub mod interrupt;
|
pub mod interrupt;
|
||||||
mod memory;
|
mod memory;
|
||||||
@@ -33,11 +34,12 @@ pub use syscall::{SyscallOutcome, SyscallResult};
|
|||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
use domain::{
|
use domain::{
|
||||||
capability::{Capability, CapabilityTable},
|
budget::{Budget, Limits},
|
||||||
|
capability::Capability,
|
||||||
endpoint::EndpointTable,
|
endpoint::EndpointTable,
|
||||||
error::KernelError,
|
error::KernelError,
|
||||||
irq::IrqRoutingTable,
|
irq::IrqRoutingTable,
|
||||||
memory::{RegionTable, frame::FrameTable, quota::Quota},
|
memory::{RegionTable, frame::FrameTable},
|
||||||
notification::NotificationTable,
|
notification::NotificationTable,
|
||||||
process::{MAX_PROCESSES, ProcessTable},
|
process::{MAX_PROCESSES, ProcessTable},
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
@@ -61,7 +63,9 @@ pub struct Kernel<P: Platform> {
|
|||||||
endpoints: EndpointTable,
|
endpoints: EndpointTable,
|
||||||
scheduler: MultiCoreScheduler,
|
scheduler: MultiCoreScheduler,
|
||||||
frames: FrameTable,
|
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,
|
regions: RegionTable,
|
||||||
irqs: IrqRoutingTable,
|
irqs: IrqRoutingTable,
|
||||||
notifications: NotificationTable,
|
notifications: NotificationTable,
|
||||||
@@ -85,7 +89,8 @@ impl<P: Platform> Kernel<P> {
|
|||||||
endpoints: EndpointTable::new(),
|
endpoints: EndpointTable::new(),
|
||||||
scheduler: MultiCoreScheduler::new(num_cores),
|
scheduler: MultiCoreScheduler::new(num_cores),
|
||||||
frames: FrameTable::new(pool_base, pool_pages),
|
frames: FrameTable::new(pool_base, pool_pages),
|
||||||
quotas: [Quota::default(); MAX_PROCESSES],
|
budgets: [Budget::EMPTY; MAX_PROCESSES],
|
||||||
|
parents: [None; MAX_PROCESSES],
|
||||||
regions: RegionTable::new(),
|
regions: RegionTable::new(),
|
||||||
irqs: IrqRoutingTable::new(),
|
irqs: IrqRoutingTable::new(),
|
||||||
notifications: NotificationTable::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).endpoints).write(EndpointTable::new());
|
||||||
addr_of_mut!((*slot).scheduler).write(MultiCoreScheduler::new(num_cores));
|
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).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).regions).write(RegionTable::new());
|
||||||
addr_of_mut!((*slot).irqs).write(IrqRoutingTable::new());
|
addr_of_mut!((*slot).irqs).write(IrqRoutingTable::new());
|
||||||
addr_of_mut!((*slot).notifications).write(NotificationTable::new());
|
addr_of_mut!((*slot).notifications).write(NotificationTable::new());
|
||||||
@@ -135,11 +141,14 @@ impl<P: Platform> Kernel<P> {
|
|||||||
priority: Priority,
|
priority: Priority,
|
||||||
time_slice: TimeSlice,
|
time_slice: TimeSlice,
|
||||||
) -> Result<(ProcessId, ThreadId), KernelError> {
|
) -> Result<(ProcessId, ThreadId), KernelError> {
|
||||||
let pid = self.processes.create(CapabilityTable::new(), None)?;
|
// Init holds every frame and every kernel object to start with.
|
||||||
if let Err(e) = self.create_address_space(pid) {
|
let everything = Limits {
|
||||||
let _ = self.processes.destroy(pid);
|
pages: self.frames.free_count(),
|
||||||
return Err(e);
|
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)?;
|
let tid = self.threads.spawn(pid, priority, time_slice, None)?;
|
||||||
self.processes.get_mut(pid)?.add_thread(tid)?;
|
self.processes.get_mut(pid)?.add_thread(tid)?;
|
||||||
|
|
||||||
@@ -211,13 +220,13 @@ impl<P: Platform> Kernel<P> {
|
|||||||
self.scheduler.next(core)
|
self.scheduler.next(core)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_process_internal(&mut self) -> Option<ProcessId> {
|
/// A process for boot code to load, its `limits` carved from `parent`.
|
||||||
let pid = self.processes.create(CapabilityTable::new(), None).ok()?;
|
pub fn create_process_internal(
|
||||||
if self.create_address_space(pid).is_err() {
|
&mut self,
|
||||||
let _ = self.processes.destroy(pid);
|
parent: ProcessId,
|
||||||
return None;
|
limits: Limits,
|
||||||
}
|
) -> Option<ProcessId> {
|
||||||
Some(pid)
|
self.new_process(Some(parent), limits).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn_thread_internal(&mut self, pid: ProcessId) -> Option<ThreadId> {
|
pub fn spawn_thread_internal(&mut self, pid: ProcessId) -> Option<ThreadId> {
|
||||||
@@ -237,7 +246,11 @@ impl<P: Platform> Kernel<P> {
|
|||||||
owner: ProcessId,
|
owner: ProcessId,
|
||||||
) -> Result<types::id::EndpointId, KernelError> {
|
) -> Result<types::id::EndpointId, KernelError> {
|
||||||
self.processes.get(owner)?;
|
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(
|
pub fn grant_capability(
|
||||||
|
|||||||
@@ -91,22 +91,13 @@ impl<P: Platform> Kernel<P> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn quota_mut(&mut self, pid: ProcessId) -> Result<&mut Quota, KernelError> {
|
fn quota_mut(&mut self, pid: ProcessId) -> Result<&mut Quota, KernelError> {
|
||||||
self.quotas
|
self.budget_mut(pid).map(|b| &mut b.pages)
|
||||||
.get_mut(pid.raw() as usize)
|
|
||||||
.ok_or(KernelError::InvalidProcess(pid))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn page_usage(&self, pid: ProcessId) -> usize {
|
pub fn page_usage(&self, pid: ProcessId) -> usize {
|
||||||
self.quotas.get(pid.raw() as usize).map_or(0, Quota::used)
|
self.budgets
|
||||||
}
|
.get(pid.raw() as usize)
|
||||||
|
.map_or(0, |b| b.pages.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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn free_page_count(&self) -> usize {
|
pub fn free_page_count(&self) -> usize {
|
||||||
@@ -121,10 +112,11 @@ impl<P: Platform> Kernel<P> {
|
|||||||
pa: PhysicalAddress,
|
pa: PhysicalAddress,
|
||||||
perms: PagePerms,
|
perms: PagePerms,
|
||||||
) -> Result<(), KernelError> {
|
) -> Result<(), KernelError> {
|
||||||
let quota = self
|
let quota = &mut self
|
||||||
.quotas
|
.budgets
|
||||||
.get_mut(pid.raw() as usize)
|
.get_mut(pid.raw() as usize)
|
||||||
.ok_or(KernelError::InvalidProcess(pid))?;
|
.ok_or(KernelError::InvalidProcess(pid))?
|
||||||
|
.pages;
|
||||||
let mut source = ChargedFrames {
|
let mut source = ChargedFrames {
|
||||||
frames: &mut self.frames,
|
frames: &mut self.frames,
|
||||||
quota,
|
quota,
|
||||||
@@ -136,11 +128,11 @@ impl<P: Platform> Kernel<P> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn create_address_space(&mut self, pid: ProcessId) -> Result<(), KernelError> {
|
pub(crate) fn create_address_space(&mut self, pid: ProcessId) -> Result<(), KernelError> {
|
||||||
let quota = self
|
let quota = &mut self
|
||||||
.quotas
|
.budgets
|
||||||
.get_mut(pid.raw() as usize)
|
.get_mut(pid.raw() as usize)
|
||||||
.ok_or(KernelError::InvalidProcess(pid))?;
|
.ok_or(KernelError::InvalidProcess(pid))?
|
||||||
*quota = Quota::default();
|
.pages;
|
||||||
let mut source = ChargedFrames {
|
let mut source = ChargedFrames {
|
||||||
frames: &mut self.frames,
|
frames: &mut self.frames,
|
||||||
quota,
|
quota,
|
||||||
@@ -152,9 +144,6 @@ impl<P: Platform> Kernel<P> {
|
|||||||
.map_err(map_error);
|
.map_err(map_error);
|
||||||
if created.is_err() {
|
if created.is_err() {
|
||||||
self.frames.free_owned_by(pid);
|
self.frames.free_owned_by(pid);
|
||||||
if let Some(q) = self.quotas.get_mut(pid.raw() as usize) {
|
|
||||||
*q = Quota::default();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
created
|
created
|
||||||
}
|
}
|
||||||
@@ -188,8 +177,8 @@ impl<P: Platform> Kernel<P> {
|
|||||||
if let Some((pa, _)) = self.user_frame(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(b) = self.budgets.get_mut(pid.raw() as usize) {
|
||||||
q.uncharge(1);
|
b.pages.uncharge(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -200,9 +189,6 @@ impl<P: Platform> Kernel<P> {
|
|||||||
self.teardown_regions(pid);
|
self.teardown_regions(pid);
|
||||||
self.platform.destroy_address_space(pid);
|
self.platform.destroy_address_space(pid);
|
||||||
self.frames.free_owned_by(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) {
|
if let Some(c) = self.heap_cursors.get_mut(pid.raw() as usize) {
|
||||||
*c = 0;
|
*c = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ impl<P: Platform> Kernel<P> {
|
|||||||
self.processes.get(owner)?;
|
self.processes.get(owner)?;
|
||||||
let id = self.regions.next_free_id().ok_or(KernelError::NoFreeSlot)?;
|
let id = self.regions.next_free_id().ok_or(KernelError::NoFreeSlot)?;
|
||||||
let count = pages.value() as usize;
|
let count = pages.value() as usize;
|
||||||
|
self.charge_object(owner, crate::budget::REGIONS)?;
|
||||||
if !self.quota_mut(owner)?.charge(count) {
|
if !self.quota_mut(owner)?.charge(count) {
|
||||||
|
self.uncharge_object(owner, crate::budget::REGIONS);
|
||||||
return Err(KernelError::OutOfMemory);
|
return Err(KernelError::OutOfMemory);
|
||||||
}
|
}
|
||||||
let Some(base) = self
|
let Some(base) = self
|
||||||
@@ -36,6 +38,7 @@ impl<P: Platform> Kernel<P> {
|
|||||||
.alloc_contiguous(count, |_| FrameUse::Region { region: id })
|
.alloc_contiguous(count, |_| FrameUse::Region { region: id })
|
||||||
else {
|
else {
|
||||||
self.quota_mut(owner)?.uncharge(count);
|
self.quota_mut(owner)?.uncharge(count);
|
||||||
|
self.uncharge_object(owner, crate::budget::REGIONS);
|
||||||
return Err(KernelError::OutOfMemory);
|
return Err(KernelError::OutOfMemory);
|
||||||
};
|
};
|
||||||
for i in 0..count {
|
for i in 0..count {
|
||||||
@@ -76,9 +79,11 @@ impl<P: Platform> Kernel<P> {
|
|||||||
if overlaps {
|
if overlaps {
|
||||||
return Err(KernelError::AddressInUse);
|
return Err(KernelError::AddressInUse);
|
||||||
}
|
}
|
||||||
|
self.charge_object(owner, crate::budget::REGIONS)?;
|
||||||
self.regions
|
self.regions
|
||||||
.insert(Region::new(owner, RegionBacking::Device, base, pages))
|
.insert(Region::new(owner, RegionBacking::Device, base, pages))
|
||||||
.ok_or(KernelError::NoFreeSlot)
|
.ok_or(KernelError::NoFreeSlot)
|
||||||
|
.inspect_err(|_| self.uncharge_object(owner, crate::budget::REGIONS))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn region_base(&self, id: RegionId) -> Option<PhysicalAddress> {
|
pub fn region_base(&self, id: RegionId) -> Option<PhysicalAddress> {
|
||||||
|
|||||||
@@ -70,7 +70,13 @@ fn any_slot() -> CapSlot {
|
|||||||
fn small_world() -> Kernel<KaniPlatform> {
|
fn small_world() -> Kernel<KaniPlatform> {
|
||||||
let mut k = Kernel::new(KaniPlatform, PhysicalAddress::from_raw(0x8400_0000), 6);
|
let mut k = Kernel::new(KaniPlatform, PhysicalAddress::from_raw(0x8400_0000), 6);
|
||||||
let (init, _) = k.boot(Priority::DEFAULT, TimeSlice::DEFAULT).unwrap();
|
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();
|
k.spawn_thread_internal(b).unwrap();
|
||||||
let ep = k.create_endpoint(init).unwrap();
|
let ep = k.create_endpoint(init).unwrap();
|
||||||
let n = k.create_notification(b).unwrap();
|
let n = k.create_notification(b).unwrap();
|
||||||
|
|||||||
@@ -301,6 +301,7 @@ impl<P: Platform> Kernel<P> {
|
|||||||
Ok(slot) => SyscallOutcome::Complete(SyscallResult::Slot(slot)),
|
Ok(slot) => SyscallOutcome::Complete(SyscallResult::Slot(slot)),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.endpoints.destroy(id);
|
self.endpoints.destroy(id);
|
||||||
|
self.uncharge_object(pid, crate::budget::ENDPOINTS);
|
||||||
SyscallOutcome::Complete(SyscallResult::Error(e))
|
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> {
|
pub fn create_notification(&mut self, owner: ProcessId) -> Result<NotificationId, KernelError> {
|
||||||
self.processes.get(owner)?;
|
self.processes.get(owner)?;
|
||||||
|
self.charge_object(owner, crate::budget::NOTIFICATIONS)?;
|
||||||
self.notifications
|
self.notifications
|
||||||
.create(owner)
|
.create(owner)
|
||||||
.ok_or(KernelError::NoFreeSlot)
|
.ok_or(KernelError::NoFreeSlot)
|
||||||
|
.inspect_err(|_| self.uncharge_object(owner, crate::budget::NOTIFICATIONS))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn create_notification_cap(
|
pub(crate) fn create_notification_cap(
|
||||||
@@ -51,6 +53,7 @@ impl<P: Platform> Kernel<P> {
|
|||||||
self.grant_cap(pid, Resource::Notification(id), rw_delegate())
|
self.grant_cap(pid, Resource::Notification(id), rw_delegate())
|
||||||
.inspect_err(|_| {
|
.inspect_err(|_| {
|
||||||
self.notifications.destroy(id);
|
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,
|
caller_pid: types::id::ProcessId,
|
||||||
create_cap: types::bounded::CapSlot,
|
create_cap: types::bounded::CapSlot,
|
||||||
_image: types::address::DataRef,
|
_image: types::address::DataRef,
|
||||||
|
limits: domain::budget::Limits,
|
||||||
) -> SyscallOutcome {
|
) -> SyscallOutcome {
|
||||||
if let Err(e) = self.check_cap(
|
if let Err(e) = self.check_cap(
|
||||||
caller_pid,
|
caller_pid,
|
||||||
@@ -50,19 +51,11 @@ impl<P: Platform> Kernel<P> {
|
|||||||
return SyscallOutcome::Complete(SyscallResult::Error(e));
|
return SyscallOutcome::Complete(SyscallResult::Error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_pid = match self
|
let new_pid = match self.new_process(Some(caller_pid), limits) {
|
||||||
.processes
|
|
||||||
.create(domain::capability::CapabilityTable::new(), None)
|
|
||||||
{
|
|
||||||
Ok(p) => p,
|
Ok(p) => p,
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
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(
|
match self.grant_cap(
|
||||||
caller_pid,
|
caller_pid,
|
||||||
domain::resource::Resource::Process(new_pid),
|
domain::resource::Resource::Process(new_pid),
|
||||||
@@ -75,9 +68,7 @@ impl<P: Platform> Kernel<P> {
|
|||||||
) {
|
) {
|
||||||
Ok(slot) => SyscallOutcome::Complete(SyscallResult::Slot(slot)),
|
Ok(slot) => SyscallOutcome::Complete(SyscallResult::Slot(slot)),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Rollback: destroy orphaned process
|
self.destroy_process(new_pid);
|
||||||
self.teardown_memory(new_pid);
|
|
||||||
let _ = self.processes.destroy(new_pid);
|
|
||||||
SyscallOutcome::Complete(SyscallResult::Error(e))
|
SyscallOutcome::Complete(SyscallResult::Error(e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,9 +83,11 @@ impl<P: Platform> Kernel<P> {
|
|||||||
Syscall::SpawnThread { entry, stack, arg } => {
|
Syscall::SpawnThread { entry, stack, arg } => {
|
||||||
self.handle_spawn_thread(pid, entry, stack, arg)
|
self.handle_spawn_thread(pid, entry, stack, arg)
|
||||||
}
|
}
|
||||||
Syscall::CreateProcess { create_cap, image } => {
|
Syscall::CreateProcess {
|
||||||
self.handle_create_process(pid, create_cap, image)
|
create_cap,
|
||||||
}
|
image,
|
||||||
|
limits,
|
||||||
|
} => self.handle_create_process(pid, create_cap, image, limits),
|
||||||
Syscall::Kill { process } => self.handle_kill(pid, process),
|
Syscall::Kill { process } => self.handle_kill(pid, process),
|
||||||
Syscall::Revoke { slot } => self.handle_revoke(pid, slot),
|
Syscall::Revoke { slot } => self.handle_revoke(pid, slot),
|
||||||
Syscall::DropCap { slot } => self.handle_drop_cap(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.destroy_notifications_owned_by(pid);
|
||||||
self.irqs.clear_owned_by(pid);
|
self.irqs.clear_owned_by(pid);
|
||||||
self.teardown_memory(pid);
|
self.teardown_memory(pid);
|
||||||
|
self.release_budget(pid);
|
||||||
|
|
||||||
let _ = self.processes.destroy(pid);
|
let _ = self.processes.destroy(pid);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ fn crowded(
|
|||||||
let parent = k.cap_ref(init, root);
|
let parent = k.cap_ref(init, root);
|
||||||
let mut pids = std::vec::Vec::new();
|
let mut pids = std::vec::Vec::new();
|
||||||
for _ in 0..n {
|
for _ in 0..n {
|
||||||
let Some(pid) = k.create_process_internal() else {
|
let Some(pid) = k.create_process_for_test() else {
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
while k
|
while k
|
||||||
@@ -173,7 +173,7 @@ fn bench_create_spawn_kill() {
|
|||||||
let rounds = 2_000;
|
let rounds = 2_000;
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
for _ in 0..rounds {
|
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.spawn_thread_internal(pid).unwrap();
|
||||||
k.destroy_process(pid);
|
k.destroy_process(pid);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ fn halt_needs_execute_on_the_cap() {
|
|||||||
#[should_panic(expected = "halt")]
|
#[should_panic(expected = "halt")]
|
||||||
fn any_holder_of_the_cap_may_halt_not_just_init() {
|
fn any_holder_of_the_cap_may_halt_not_just_init() {
|
||||||
let (mut k, init) = booted();
|
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);
|
let tid = k.spawn_thread_for_test(other, Priority::DEFAULT, TimeSlice::DEFAULT);
|
||||||
k.grant_cap_for_test(
|
k.grant_cap_for_test(
|
||||||
other,
|
other,
|
||||||
@@ -207,7 +207,7 @@ fn granting_needs_a_registered_board() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn granting_needs_the_regions_to_be_inits() {
|
fn granting_needs_the_regions_to_be_inits() {
|
||||||
let (mut k, init) = kernel_with_process();
|
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();
|
k.register_board(other, &board()).unwrap();
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
k.grant_boot_authority(init, &board(), &mut [None; 2]),
|
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 {
|
let target_slot = match k.handle_syscall(Syscall::CreateProcess {
|
||||||
create_cap: CapSlot::new(0).unwrap(),
|
create_cap: CapSlot::new(0).unwrap(),
|
||||||
image: DataRef::empty(),
|
image: DataRef::empty(),
|
||||||
|
limits: super::helpers::CHILD_LIMITS,
|
||||||
}) {
|
}) {
|
||||||
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
||||||
_ => panic!("expected slot"),
|
_ => panic!("expected slot"),
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ impl World {
|
|||||||
Syscall::CreateProcess {
|
Syscall::CreateProcess {
|
||||||
create_cap: CapSlot::new(0).unwrap(),
|
create_cap: CapSlot::new(0).unwrap(),
|
||||||
image: DataRef::empty(),
|
image: DataRef::empty(),
|
||||||
|
limits: super::helpers::CHILD_LIMITS,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
self.pids[i] = self.k.process_of_cap_for_test(cap);
|
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)
|
(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> {
|
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(
|
pub fn spawn_thread_for_test(
|
||||||
&mut self,
|
&mut self,
|
||||||
pid: ProcessId,
|
pid: ProcessId,
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ impl World {
|
|||||||
caps[i] = match k.handle_syscall(Syscall::CreateProcess {
|
caps[i] = match k.handle_syscall(Syscall::CreateProcess {
|
||||||
create_cap: CapSlot::new(0).unwrap(),
|
create_cap: CapSlot::new(0).unwrap(),
|
||||||
image: DataRef::empty(),
|
image: DataRef::empty(),
|
||||||
|
limits: super::helpers::CHILD_LIMITS,
|
||||||
}) {
|
}) {
|
||||||
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
||||||
_ => panic!("create process"),
|
_ => panic!("create process"),
|
||||||
@@ -164,6 +165,7 @@ impl World {
|
|||||||
let fresh = match self.k.handle_syscall(Syscall::CreateProcess {
|
let fresh = match self.k.handle_syscall(Syscall::CreateProcess {
|
||||||
create_cap: CapSlot::new(0).unwrap(),
|
create_cap: CapSlot::new(0).unwrap(),
|
||||||
image: DataRef::empty(),
|
image: DataRef::empty(),
|
||||||
|
limits: super::helpers::CHILD_LIMITS,
|
||||||
}) {
|
}) {
|
||||||
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
||||||
_ => panic!("re-create process"),
|
_ => panic!("re-create process"),
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ impl Ipc {
|
|||||||
match k.handle_syscall(Syscall::CreateProcess {
|
match k.handle_syscall(Syscall::CreateProcess {
|
||||||
create_cap: CapSlot::new(0).unwrap(),
|
create_cap: CapSlot::new(0).unwrap(),
|
||||||
image: DataRef::empty(),
|
image: DataRef::empty(),
|
||||||
|
limits: super::helpers::CHILD_LIMITS,
|
||||||
}) {
|
}) {
|
||||||
SyscallOutcome::Complete(SyscallResult::Slot(_)) => {}
|
SyscallOutcome::Complete(SyscallResult::Slot(_)) => {}
|
||||||
_ => panic!("create process"),
|
_ => 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 mut t = Ipc::new();
|
||||||
let (ta, tb, b, b_recv) = (t.ta, t.tb, t.b, t.b_recv);
|
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.
|
// 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 =
|
let tc =
|
||||||
t.k.spawn_thread_for_test(c, Priority::DEFAULT, TimeSlice::DEFAULT);
|
t.k.spawn_thread_for_test(c, Priority::DEFAULT, TimeSlice::DEFAULT);
|
||||||
let c_send =
|
let c_send =
|
||||||
|
|||||||
@@ -64,8 +64,8 @@ impl World {
|
|||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
let (mut k, p0) = kernel_with_process();
|
let (mut k, p0) = kernel_with_process();
|
||||||
let t0 = k.current_thread().unwrap();
|
let t0 = k.current_thread().unwrap();
|
||||||
let p1 = k.create_process_internal().unwrap();
|
let p1 = k.create_process_for_test().unwrap();
|
||||||
let p2 = k.create_process_internal().unwrap();
|
let p2 = k.create_process_for_test().unwrap();
|
||||||
// Endpoints and the notification belong to init, which never dies.
|
// Endpoints and the notification belong to init, which never dies.
|
||||||
let eps = [
|
let eps = [
|
||||||
k.create_endpoint(p0).unwrap(),
|
k.create_endpoint(p0).unwrap(),
|
||||||
@@ -168,7 +168,7 @@ impl World {
|
|||||||
/// Kills process `pi` and brings it back with fresh threads and caps.
|
/// Kills process `pi` and brings it back with fresh threads and caps.
|
||||||
fn reincarnate(&mut self, pi: usize) {
|
fn reincarnate(&mut self, pi: usize) {
|
||||||
self.k.destroy_process(self.procs[pi].pid);
|
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];
|
let t0 = self.procs[0].threads[0];
|
||||||
Self::equip(
|
Self::equip(
|
||||||
&mut self.k,
|
&mut self.k,
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ fn two_processes() -> (
|
|||||||
let cap_b = match k.handle_syscall(Syscall::CreateProcess {
|
let cap_b = match k.handle_syscall(Syscall::CreateProcess {
|
||||||
create_cap: CapSlot::new(0).unwrap(),
|
create_cap: CapSlot::new(0).unwrap(),
|
||||||
image: DataRef::empty(),
|
image: DataRef::empty(),
|
||||||
|
limits: super::helpers::CHILD_LIMITS,
|
||||||
}) {
|
}) {
|
||||||
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
||||||
_ => panic!("create process"),
|
_ => panic!("create process"),
|
||||||
@@ -219,7 +220,7 @@ fn page_tables_count_against_the_process_quota() {
|
|||||||
let (mut k, pid) = kernel_with_process();
|
let (mut k, pid) = kernel_with_process();
|
||||||
let used = k.page_usage(pid);
|
let used = k.page_usage(pid);
|
||||||
// Room for exactly one data page plus the leaf table it needs.
|
// 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_some());
|
||||||
assert!(request(&mut k, 1).is_none(), "quota should be exhausted");
|
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 {
|
let cap_b = match k.handle_syscall(Syscall::CreateProcess {
|
||||||
create_cap: CapSlot::new(0).unwrap(),
|
create_cap: CapSlot::new(0).unwrap(),
|
||||||
image: DataRef::empty(),
|
image: DataRef::empty(),
|
||||||
|
limits: super::helpers::CHILD_LIMITS,
|
||||||
}) {
|
}) {
|
||||||
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
||||||
_ => panic!("create process"),
|
_ => 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 (mut k, pid_a, _, pid_b, _, cap_b) = two_processes();
|
||||||
let src = request(&mut k, 1).unwrap();
|
let src = request(&mut k, 1).unwrap();
|
||||||
let used = k.page_usage(pid_b);
|
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));
|
assert!(!map_page(&mut k, cap_b, src, 0x10000));
|
||||||
// the page stays with the caller
|
// the page stays with the caller
|
||||||
assert!(k.platform_mut().mapping(pid_a, src).is_some());
|
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 {
|
let outcome = k.handle_syscall(Syscall::CreateProcess {
|
||||||
create_cap: CapSlot::new(0).unwrap(),
|
create_cap: CapSlot::new(0).unwrap(),
|
||||||
image: DataRef::empty(),
|
image: DataRef::empty(),
|
||||||
|
limits: super::helpers::CHILD_LIMITS,
|
||||||
});
|
});
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
outcome,
|
outcome,
|
||||||
@@ -61,6 +62,7 @@ fn create_process_without_cap_fails() {
|
|||||||
let outcome = k.handle_syscall(Syscall::CreateProcess {
|
let outcome = k.handle_syscall(Syscall::CreateProcess {
|
||||||
create_cap: CapSlot::new(5).unwrap(),
|
create_cap: CapSlot::new(5).unwrap(),
|
||||||
image: DataRef::empty(),
|
image: DataRef::empty(),
|
||||||
|
limits: super::helpers::CHILD_LIMITS,
|
||||||
});
|
});
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
outcome,
|
outcome,
|
||||||
@@ -75,6 +77,7 @@ fn kill_removes_process() {
|
|||||||
let slot = match k.handle_syscall(Syscall::CreateProcess {
|
let slot = match k.handle_syscall(Syscall::CreateProcess {
|
||||||
create_cap: CapSlot::new(0).unwrap(),
|
create_cap: CapSlot::new(0).unwrap(),
|
||||||
image: DataRef::empty(),
|
image: DataRef::empty(),
|
||||||
|
limits: super::helpers::CHILD_LIMITS,
|
||||||
}) {
|
}) {
|
||||||
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
||||||
_ => panic!("expected slot"),
|
_ => panic!("expected slot"),
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ fn create_process(k: &mut Kernel<MockPlatform>) -> CapSlot {
|
|||||||
match k.handle_syscall(Syscall::CreateProcess {
|
match k.handle_syscall(Syscall::CreateProcess {
|
||||||
create_cap: CapSlot::new(0).unwrap(),
|
create_cap: CapSlot::new(0).unwrap(),
|
||||||
image: DataRef::empty(),
|
image: DataRef::empty(),
|
||||||
|
limits: super::helpers::CHILD_LIMITS,
|
||||||
}) {
|
}) {
|
||||||
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
||||||
_ => panic!("create process"),
|
_ => 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
|
// The kernel finds a page through the platform's page table but only
|
||||||
// believes the answer if its own frame table agrees.
|
// believes the answer if its own frame table agrees.
|
||||||
let (mut k, a) = kernel_with_process();
|
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(
|
k.map_fresh_range(
|
||||||
b,
|
b,
|
||||||
VirtualAddress::from_raw(0x50000),
|
VirtualAddress::from_raw(0x50000),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ mod mock_platform;
|
|||||||
mod abi;
|
mod abi;
|
||||||
mod bench;
|
mod bench;
|
||||||
mod board;
|
mod board;
|
||||||
|
mod budget;
|
||||||
mod capability;
|
mod capability;
|
||||||
mod captree_model;
|
mod captree_model;
|
||||||
mod exit;
|
mod exit;
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ impl Tree {
|
|||||||
let create = || Syscall::CreateProcess {
|
let create = || Syscall::CreateProcess {
|
||||||
create_cap: CapSlot::new(0).unwrap(),
|
create_cap: CapSlot::new(0).unwrap(),
|
||||||
image: DataRef::empty(),
|
image: DataRef::empty(),
|
||||||
|
limits: super::helpers::CHILD_LIMITS,
|
||||||
};
|
};
|
||||||
let a_to_b = slot_of(k.handle_syscall(create()));
|
let a_to_b = slot_of(k.handle_syscall(create()));
|
||||||
let a_to_c = 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]
|
#[test]
|
||||||
fn killing_a_thread_running_on_another_core_stops_that_core() {
|
fn killing_a_thread_running_on_another_core_stops_that_core() {
|
||||||
let (mut k, init) = smp(2);
|
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();
|
let t = k.spawn_thread_internal(child).unwrap();
|
||||||
on(&mut k, core(1));
|
on(&mut k, core(1));
|
||||||
k.platform_mut().take_ipi(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 _ = 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() {
|
if std::env::var("STORM_DEBUG").is_ok() {
|
||||||
std::eprintln!("no hub: free pages {}", k.free_page_count());
|
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 {
|
k.handle_syscall(domain::syscall::Syscall::CreateProcess {
|
||||||
create_cap: CapSlot::new(0).unwrap(),
|
create_cap: CapSlot::new(0).unwrap(),
|
||||||
image: DataRef::empty(),
|
image: DataRef::empty(),
|
||||||
|
limits: super::helpers::CHILD_LIMITS,
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
let pid = k.process_of_cap_for_test(cap);
|
let pid = k.process_of_cap_for_test(cap);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
use super::{call0, raw, slot_of};
|
use super::{call0, raw, slot_of};
|
||||||
use types::address::{EntryPoint, StackPointer};
|
use types::address::{EntryPoint, StackPointer};
|
||||||
use types::bounded::CapSlot;
|
use types::bounded::CapSlot;
|
||||||
|
use types::budget::Limits;
|
||||||
use types::id::ProcessId;
|
use types::id::ProcessId;
|
||||||
use types::syscall_abi::*;
|
use types::syscall_abi::*;
|
||||||
use types::time::Ticks;
|
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)
|
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(
|
slot_of(raw(
|
||||||
SYS_CREATE_PROCESS,
|
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()
|
.ok()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
use server_framework::Server;
|
use server_framework::Server;
|
||||||
use types::bounded::CapSlot;
|
use types::bounded::CapSlot;
|
||||||
|
use types::budget::Limits;
|
||||||
|
|
||||||
use crate::protocol::{AppId, SessionRequest, SessionResponse};
|
use crate::protocol::{AppId, SessionRequest, SessionResponse};
|
||||||
|
|
||||||
const MAX_APPS: usize = 16;
|
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)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct AppEntry {
|
pub struct AppEntry {
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
@@ -77,7 +86,10 @@ impl SessionServer {
|
|||||||
if self.running_mut(app).is_none_or(|r| r.is_some()) {
|
if self.running_mut(app).is_none_or(|r| r.is_some()) {
|
||||||
return SessionResponse::Error;
|
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;
|
return SessionResponse::Error;
|
||||||
};
|
};
|
||||||
if let Some(r) = self.running_mut(app) {
|
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 badge;
|
||||||
pub mod bootinfo;
|
pub mod bootinfo;
|
||||||
pub mod bounded;
|
pub mod bounded;
|
||||||
|
pub mod budget;
|
||||||
pub mod display;
|
pub mod display;
|
||||||
pub mod generation;
|
pub mod generation;
|
||||||
pub mod id;
|
pub mod id;
|
||||||
|
|||||||
14
docs/ipc.md
14
docs/ipc.md
@@ -160,6 +160,20 @@ set it stays masked.
|
|||||||
- If the caller dies, the reply slot is simply stale and `Reply` is a
|
- If the caller dies, the reply slot is simply stale and `Reply` is a
|
||||||
successful no-op.
|
successful no-op.
|
||||||
|
|
||||||
|
## Budgets
|
||||||
|
|
||||||
|
Every process has a budget: frames (its pages, page tables and regions'
|
||||||
|
pages) and how many endpoints, notifications and regions it may own. Init
|
||||||
|
starts with the whole frame pool and every table's capacity.
|
||||||
|
`CreateProcess(create, pages, endpoints, notifications, regions)` carves
|
||||||
|
the child's limits out of the caller's unused budget (`OutOfMemory` if it
|
||||||
|
does not fit); boot carves the servers it loads out of init's. Over budget,
|
||||||
|
page requests fail with `OutOfMemory` and object creation with `NoFreeSlot`.
|
||||||
|
The limits of all live processes never add up to more than init got.
|
||||||
|
|
||||||
|
When a process dies its limits go back to its parent, and its children
|
||||||
|
pass to that parent, so their budgets return there in turn.
|
||||||
|
|
||||||
## Death
|
## Death
|
||||||
|
|
||||||
Destroying an endpoint or notification wakes every thread blocked on it with
|
Destroying an endpoint or notification wakes every thread blocked on it with
|
||||||
|
|||||||
Reference in New Issue
Block a user