From 052ca0e4e3018d6cc90ff508a6630f6f2e0db5e2 Mon Sep 17 00:00:00 2001 From: Matthew Leach Date: Fri, 1 May 2026 20:25:12 +0100 Subject: [PATCH] libkernel: smalloc: add claim_region for downstream allocators Introduce Smalloc::claim_region, which removes a sub-region from the free memory pool and transfers ownership to another allocator. The region is punched out of the memory list rather than being added to the reservation list. --- libkernel/src/memory/allocators/smalloc.rs | 198 +++++++++++++++++++++ 1 file changed, 198 insertions(+) diff --git a/libkernel/src/memory/allocators/smalloc.rs b/libkernel/src/memory/allocators/smalloc.rs index 2ae3362..a8b7c12 100644 --- a/libkernel/src/memory/allocators/smalloc.rs +++ b/libkernel/src/memory/allocators/smalloc.rs @@ -385,6 +385,48 @@ impl> Smalloc { Ok(()) } + /// Removes `region` from the free memory pool, allowing it to be used by a + /// downstream allocator. + /// + /// `region` is *not* added to the reservation list, so any pre-existing + /// reservations within it (e.g. the kernel image, or metadata that smalloc + /// allocated for the downstream allocator) remain visible via `self.res` + /// and can still be consulted by the downstream allocator. + /// + /// Returns [`KernelError::InvalidValue`] if `region` is not entirely + /// covered by a single known memory region. + pub fn claim_region(&mut self, region: PhysMemoryRegion) -> Result<()> { + // Ensure `region` is completely covered by a memory-backed region. + if !self.memory.iter().any(|m| m.contains(region)) { + return Err(KernelError::InvalidValue); + } + + // Punching may turn one entry into two! + if self.memory.requires_reallocation() { + self.grow_region_list(RegionListType::Mem)?; + } + + // Resolve the index after the potential grow above. + let containing_idx = self + .memory + .iter() + .position(|m| m.contains(region)) + .expect("memory region disappeared after grow"); + + let containing = self.memory[containing_idx]; + self.memory.remove_at(containing_idx); + + let (left, right) = containing.punch_hole(region); + if let Some(l) = left { + self.memory.insert_region(l); + } + if let Some(r) = right { + self.memory.insert_region(r); + } + + Ok(()) + } + /// Allocates a single page-aligned page and returns its frame number. pub fn alloc_page(&mut self) -> Result { let pa = self.alloc(PAGE_SIZE, PAGE_SIZE)?; @@ -1310,4 +1352,160 @@ mod tests { assert_eq!(smalloc.iter_free().count(), 0); } + + #[test] + fn claim_region_middle_splits_into_two() { + let mut smalloc = get_smalloc(); + smalloc + .add_memory(PhysMemoryRegion::new(PA::from_value(0x1000), 0x1000)) // 0x1000-0x2000 + .unwrap(); + + smalloc + .claim_region(PhysMemoryRegion::new(PA::from_value(0x1400), 0x400)) // 0x1400-0x1800 + .unwrap(); + + let regions: std::vec::Vec<_> = smalloc.iter_memory().collect(); + assert_eq!(regions.len(), 2); + check_region(®ions[0], 0x1000, 0x400); + check_region(®ions[1], 0x1800, 0x800); + + // The claimed region must not appear in iter_free either. + let free: std::vec::Vec<_> = smalloc.iter_free().collect(); + assert_eq!(free.len(), 2); + check_region(&free[0], 0x1000, 0x400); + check_region(&free[1], 0x1800, 0x800); + } + + #[test] + fn claim_region_prefix_leaves_suffix() { + let mut smalloc = get_smalloc(); + smalloc + .add_memory(PhysMemoryRegion::new(PA::from_value(0x1000), 0x1000)) // 0x1000-0x2000 + .unwrap(); + + smalloc + .claim_region(PhysMemoryRegion::new(PA::from_value(0x1000), 0x400)) // 0x1000-0x1400 + .unwrap(); + + let regions: std::vec::Vec<_> = smalloc.iter_memory().collect(); + assert_eq!(regions.len(), 1); + check_region(®ions[0], 0x1400, 0xc00); + } + + #[test] + fn claim_region_suffix_leaves_prefix() { + let mut smalloc = get_smalloc(); + smalloc + .add_memory(PhysMemoryRegion::new(PA::from_value(0x1000), 0x1000)) // 0x1000-0x2000 + .unwrap(); + + smalloc + .claim_region(PhysMemoryRegion::new(PA::from_value(0x1c00), 0x400)) // 0x1c00-0x2000 + .unwrap(); + + let regions: std::vec::Vec<_> = smalloc.iter_memory().collect(); + assert_eq!(regions.len(), 1); + check_region(®ions[0], 0x1000, 0xc00); + } + + #[test] + fn claim_region_exact_match_removes() { + let mut smalloc = get_smalloc(); + smalloc + .add_memory(PhysMemoryRegion::new(PA::from_value(0x1000), 0x1000)) + .unwrap(); + + smalloc + .claim_region(PhysMemoryRegion::new(PA::from_value(0x1000), 0x1000)) + .unwrap(); + + assert_eq!(smalloc.iter_memory().count(), 0); + assert_eq!(smalloc.iter_free().count(), 0); + } + + #[test] + fn claim_region_preserves_inner_reservation() { + let mut smalloc = get_smalloc(); + smalloc + .add_memory(PhysMemoryRegion::new(PA::from_value(0x1000), 0x1000)) + .unwrap(); + // A pre-existing reservation inside the to-be-claimed region (e.g. + // the kernel image) must remain in the reservation list so a + // downstream allocator can still observe it. + smalloc + .add_reservation(PhysMemoryRegion::new(PA::from_value(0x1400), 0x100)) + .unwrap(); + + smalloc + .claim_region(PhysMemoryRegion::new(PA::from_value(0x1000), 0x1000)) + .unwrap(); + + let res: std::vec::Vec<_> = smalloc.res.iter().collect(); + assert_eq!(res.len(), 1); + check_region(&res[0], 0x1400, 0x100); + } + + #[test] + fn claim_region_unknown_fails() { + let mut smalloc = get_smalloc(); + smalloc + .add_memory(PhysMemoryRegion::new(PA::from_value(0x1000), 0x1000)) + .unwrap(); + + // Region wholly outside any known memory. + let result = smalloc.claim_region(PhysMemoryRegion::new(PA::from_value(0x5000), 0x100)); + assert!(matches!(result, Err(KernelError::InvalidValue))); + } + + #[test] + fn claim_region_partial_coverage_fails() { + let mut smalloc = get_smalloc(); + smalloc + .add_memory(PhysMemoryRegion::new(PA::from_value(0x1000), 0x1000)) // 0x1000-0x2000 + .unwrap(); + + // Spans the end of the known region. + let result = smalloc.claim_region(PhysMemoryRegion::new(PA::from_value(0x1f00), 0x200)); + assert!(matches!(result, Err(KernelError::InvalidValue))); + } + + #[test] + fn claim_region_does_not_straddle_two_memory_regions() { + let mut smalloc = get_smalloc(); + smalloc + .add_memory(PhysMemoryRegion::new(PA::from_value(0x1000), 0x500)) + .unwrap(); + smalloc + .add_memory(PhysMemoryRegion::new(PA::from_value(0x2000), 0x500)) + .unwrap(); + + // Spans the gap between the two regions; not contained in either. + let result = smalloc.claim_region(PhysMemoryRegion::new(PA::from_value(0x1400), 0x800)); + assert!(matches!(result, Err(KernelError::InvalidValue))); + } + + #[test] + fn claim_region_subsequent_alloc_avoids_claim() { + let mut smalloc = get_smalloc(); + smalloc + .add_memory(PhysMemoryRegion::new(PA::from_value(0x1000), 0x1000)) + .unwrap(); + + smalloc + .claim_region(PhysMemoryRegion::new(PA::from_value(0x1400), 0x400)) + .unwrap(); + + // Subsequent allocations must not land inside the claimed region. + for _ in 0..16 { + let pa = match smalloc.alloc(0x100, 0x100) { + Ok(pa) => pa, + Err(_) => break, + }; + assert!( + pa.value() + 0x100 <= 0x1400 || pa.value() >= 0x1800, + "allocation at {:#x} overlaps claimed region", + pa.value() + ); + } + } }