//! 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, 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); }