feat(kernel): per-process budgets (pages, endpoints, notifs, regions) carved from creator, returned on death; init owns pool

This commit is contained in:
2026-10-27 15:08:58 +01:00
parent 7415bab7f5
commit 4049ba5007
46 changed files with 672 additions and 119 deletions

View 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);
}