feat(kernel): per-process budgets (pages, endpoints, notifs, regions) carved from creator, returned on death; init owns pool
This commit is contained in:
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);
|
||||
}
|
||||
Reference in New Issue
Block a user