Skip to content

Commit 2a84033

Browse files
committed
Memory management: per-process VA range, brk frontier tracking, noreserve/LOW_2G support
- Vmem and PageManager accept a configurable VA range instead of always using the platform-wide TASK_ADDR_MIN..TASK_ADDR_MAX, enabling per-process address space partitioning. - Track brk_base and brk_frontier for correct heap management: brk_base prevents shrinking below the initial program break, brk_frontier tracks the actual page-aligned heap high-water mark. - Add CreatePagesFlags: FD_WRITABLE (shared file-backed VM_MAYWRITE), NORESERVE (sparse reservations), LOW_2G (MAP_32BIT ceiling). - VmArea gains noreserve field, propagated through all split/merge paths. - create_mapping uses a retry loop for non-fixed allocations when the host rejects an address inside the guest partition. - get_unmmaped_area supports require_low_2g and max_start narrowing. - handle_page_fault returns AllocationFailed instead of panicking. - VmemResizeError::OutOfRange triggers move_mappings fallback in remap.
1 parent 046443d commit 2a84033

8 files changed

Lines changed: 418 additions & 98 deletions

File tree

litebox/src/mm/linux.rs

Lines changed: 266 additions & 65 deletions
Large diffs are not rendered by default.

litebox/src/mm/mod.rs

Lines changed: 125 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,41 @@ where
3434
Platform: RawSyncPrimitivesProvider + PageManagementProvider<ALIGN>,
3535
{
3636
vmem: RwLock<Platform, Vmem<Platform, ALIGN>>,
37+
/// Lower bound (inclusive) of the VA range (cached for lock-free checks).
38+
addr_min: usize,
39+
/// Upper bound (exclusive) of the VA range (cached for lock-free checks).
40+
addr_max: usize,
3741
}
3842

3943
impl<Platform, const ALIGN: usize> PageManager<Platform, ALIGN>
4044
where
4145
Platform: RawSyncPrimitivesProvider + PageManagementProvider<ALIGN>,
4246
{
43-
/// Create a new `PageManager` instance.
44-
pub fn new(litebox: &LiteBox<Platform>) -> Self {
45-
let vmem = RwLock::new(linux::Vmem::new(litebox.x.platform));
46-
Self { vmem }
47+
/// Lower bound (inclusive) of the managed VA range.
48+
pub fn addr_min(&self) -> usize {
49+
self.addr_min
50+
}
51+
52+
/// Upper bound (exclusive) of the managed VA range.
53+
pub fn addr_max(&self) -> usize {
54+
self.addr_max
55+
}
56+
57+
/// Create a new `PageManager` instance for the given VA `range`.
58+
///
59+
/// For a single-process setup, pass the full platform range
60+
/// `Platform::TASK_ADDR_MIN..Platform::TASK_ADDR_MAX`. For multi-process
61+
/// setups, pass the sub-range obtained from
62+
/// [`AddressSpaceProvider::address_space_range()`](crate::platform::AddressSpaceProvider::address_space_range).
63+
pub fn new(litebox: &LiteBox<Platform>, range: Range<usize>) -> Self {
64+
let addr_min = range.start;
65+
let addr_max = range.end;
66+
let vmem = RwLock::new(linux::Vmem::new(litebox.x.platform, range));
67+
Self {
68+
vmem,
69+
addr_min,
70+
addr_max,
71+
}
4772
}
4873

4974
/// Create a mapping with the given flags.
@@ -289,7 +314,40 @@ where
289314
pub fn set_initial_brk(&self, brk: usize) {
290315
let mut vmem = self.vmem.write();
291316
assert_eq!(vmem.brk, 0, "initial brk is already set");
317+
vmem.brk_base = brk;
292318
vmem.brk = brk;
319+
vmem.brk_frontier = brk.next_multiple_of(linux::PAGE_SIZE);
320+
}
321+
322+
/// Returns the current logical program break without modifying mappings.
323+
pub fn current_brk(&self) -> usize {
324+
self.vmem.read().brk
325+
}
326+
327+
/// Returns the current page-aligned heap frontier that `brk` growth uses.
328+
pub fn current_brk_frontier(&self) -> usize {
329+
self.vmem.read().brk_frontier
330+
}
331+
332+
/// Advance the minimum program break, current break, and heap frontier to
333+
/// at least `min_brk` without allocating new heap pages.
334+
///
335+
/// This is used only when already-mapped non-heap pages, such as a
336+
/// trampoline at the heap frontier, occupy space that future `brk` growth
337+
/// must skip over. The skipped gap becomes a persistent floor: later
338+
/// `brk` shrinks must not re-enter it, or subsequent growth would collide
339+
/// with the same non-heap pages again.
340+
pub fn ensure_brk_past(&self, min_brk: usize) {
341+
let mut vmem = self.vmem.write();
342+
if vmem.brk_base < min_brk {
343+
vmem.brk_base = min_brk;
344+
}
345+
if vmem.brk < min_brk {
346+
vmem.brk = min_brk;
347+
}
348+
if vmem.brk_frontier < min_brk {
349+
vmem.brk_frontier = min_brk;
350+
}
293351
}
294352

295353
/// Set the program break to the given address.
@@ -319,10 +377,15 @@ where
319377
// Calling `brk` with 0 can be used to find the current location of the program break.
320378
return Ok(vmem.brk);
321379
}
380+
if brk < vmem.brk_base {
381+
// Linux keeps the current break unchanged when the request is
382+
// below the permitted heap base.
383+
return Ok(vmem.brk);
384+
}
322385

323-
let old_brk = vmem.brk.next_multiple_of(linux::PAGE_SIZE);
386+
let old_brk = vmem.brk_frontier;
324387
let new_brk = brk.next_multiple_of(linux::PAGE_SIZE);
325-
if vmem.brk >= brk {
388+
if new_brk < old_brk {
326389
// Shrink the memory region
327390
let brk = match unsafe {
328391
vmem.remove_mapping(
@@ -331,6 +394,7 @@ where
331394
} {
332395
Ok(()) => {
333396
vmem.brk = brk;
397+
vmem.brk_frontier = new_brk;
334398
brk
335399
}
336400
Err(_) => {
@@ -340,20 +404,23 @@ where
340404
return Ok(brk);
341405
}
342406

343-
if vmem.overlapping(old_brk..new_brk).next().is_some() {
344-
return Err(MappingError::OutOfMemory);
345-
}
346-
if let Some(range) = PageRange::<ALIGN>::new(old_brk, new_brk) {
347-
let (suggested_address, length) = range.start_and_length();
348-
let perms = MemoryRegionPermissions::READ | MemoryRegionPermissions::WRITE;
349-
unsafe {
350-
vmem.create_pages(
351-
Some(suggested_address),
352-
length,
353-
CreatePagesFlags::FIXED_ADDR | CreatePagesFlags::POPULATE_PAGES_IMMEDIATELY,
354-
perms,
355-
)
356-
}?;
407+
if new_brk > old_brk {
408+
if vmem.overlapping(old_brk..new_brk).next().is_some() {
409+
return Err(MappingError::OutOfMemory);
410+
}
411+
if let Some(range) = PageRange::<ALIGN>::new(old_brk, new_brk) {
412+
let (suggested_address, length) = range.start_and_length();
413+
let perms = MemoryRegionPermissions::READ | MemoryRegionPermissions::WRITE;
414+
unsafe {
415+
vmem.create_pages(
416+
Some(suggested_address),
417+
length,
418+
CreatePagesFlags::FIXED_ADDR | CreatePagesFlags::POPULATE_PAGES_IMMEDIATELY,
419+
perms,
420+
)
421+
}?;
422+
}
423+
vmem.brk_frontier = new_brk;
357424
}
358425
vmem.brk = brk;
359426
Ok(brk)
@@ -381,7 +448,9 @@ where
381448

382449
// reset brk
383450
let mut vmem = self.vmem.write();
451+
vmem.brk_base = 0;
384452
vmem.brk = 0;
453+
vmem.brk_frontier = 0;
385454

386455
Ok(())
387456
}
@@ -442,6 +511,24 @@ where
442511
Err(linux::VmemResizeError::NotExist(_)) => Err(RemapError::AlreadyUnallocated),
443512
Err(linux::VmemResizeError::InvalidAddr { .. }) => Err(RemapError::AlreadyAllocated),
444513
Err(linux::VmemResizeError::OutOfMemory) => Err(RemapError::OutOfMemory),
514+
Err(linux::VmemResizeError::OutOfRange) => {
515+
// Expanded range exceeds address space limits — try moving
516+
if !may_move {
517+
return Err(RemapError::OutOfMemory);
518+
}
519+
match unsafe {
520+
vmem.move_mappings(
521+
old_range,
522+
None,
523+
NonZeroPageSize::new(new_size).ok_or(RemapError::Unaligned)?,
524+
)
525+
} {
526+
Ok(new_addr) => Ok(new_addr),
527+
Err(linux::VmemMoveError::OutOfMemory) => Err(RemapError::OutOfMemory),
528+
Err(linux::VmemMoveError::UnAligned) => Err(RemapError::Unaligned),
529+
Err(linux::VmemMoveError::RemapError(err)) => Err(err),
530+
}
531+
}
445532
}
446533
}
447534

@@ -605,16 +692,19 @@ where
605692
///
606693
/// The `range` must be an already-mapped region with the given `permissions`.
607694
#[must_use]
695+
#[allow(clippy::fn_params_excessive_bools)]
608696
pub unsafe fn register_existing_mapping(
609697
&self,
610698
range: PageRange<ALIGN>,
611699
permissions: MemoryRegionPermissions,
612700
is_file_backed: bool,
613701
replace: bool,
614702
shared: bool,
703+
fd_writable: bool,
615704
) -> Option<()> {
616705
let vma = VmArea::new(
617-
VmFlags::from(permissions) | VmFlags::may_flags_for_mapping(shared, is_file_backed),
706+
VmFlags::from(permissions)
707+
| VmFlags::may_flags_for_mapping(shared, is_file_backed, fd_writable),
618708
is_file_backed,
619709
);
620710
let mut vmem = self.vmem.write();
@@ -665,22 +755,25 @@ where
665755
///
666756
/// # Safety
667757
///
668-
/// This should only be called from the kernel page fault handler.
758+
/// This must only be called while servicing a real page fault for the
759+
/// current address space, and `fault_addr` / `error_code` must come from
760+
/// that trap context. Depending on the platform, that may be a kernel fault
761+
/// handler or an opted-in user-mode fault path.
669762
pub unsafe fn handle_page_fault(
670763
&self,
671764
fault_addr: usize,
672765
error_code: u64,
673766
) -> Result<(), PageFaultError> {
674767
let fault_addr = fault_addr & !(ALIGN - 1);
675-
if !(Platform::TASK_ADDR_MIN..Platform::TASK_ADDR_MAX).contains(&fault_addr) {
768+
if !(self.addr_min..self.addr_max).contains(&fault_addr) {
676769
return Err(PageFaultError::AccessError("Invalid address"));
677770
}
678771

679772
let mut vmem = self.vmem.write();
680773
// Find the range closest to the fault address
681774
let (start, vma) = {
682775
let (r, vma) = vmem
683-
.overlapping(fault_addr..Platform::TASK_ADDR_MAX)
776+
.overlapping(fault_addr..vmem.addr_max())
684777
.next()
685778
.ok_or(PageFaultError::AccessError("no mapping"))?;
686779
(r.start, *vma)
@@ -692,7 +785,7 @@ where
692785
}
693786

694787
if !vmem
695-
.overlapping(Platform::TASK_ADDR_MIN..fault_addr)
788+
.overlapping(vmem.addr_min()..fault_addr)
696789
.next_back()
697790
.is_none_or(|(prev_range, prev_vma)| {
698791
// Enforce gap between stack and other preceding non-stack mappings.
@@ -708,15 +801,19 @@ where
708801
let Some(range) = PageRange::new(fault_addr, start) else {
709802
unreachable!()
710803
};
711-
if let Err(err) = unsafe {
804+
if unsafe {
712805
vmem.insert_mapping(
713806
range,
714807
vma,
715808
false,
809+
vma.noreserve(),
716810
crate::platform::page_mgmt::FixedAddressBehavior::NoReplace,
811+
false,
717812
)
718-
} {
719-
unimplemented!("failed to grow stack: {:?}", err)
813+
}
814+
.is_err()
815+
{
816+
return Err(PageFaultError::AllocationFailed);
720817
}
721818
}
722819

litebox/src/mm/tests.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,10 @@ fn collect_mappings(vmm: &Vmem<DummyVmemBackend, PAGE_SIZE>) -> Vec<Range<usize>
8686
fn test_vmm_mapping() {
8787
let start_addr: usize = 0x1_0000;
8888
let range = PageRange::new(start_addr, start_addr + 12 * PAGE_SIZE).unwrap();
89-
let mut vmm = Vmem::new(&DummyVmemBackend);
89+
let mut vmm = Vmem::new(
90+
&DummyVmemBackend,
91+
DummyVmemBackend::TASK_ADDR_MIN..DummyVmemBackend::TASK_ADDR_MAX,
92+
);
9093

9194
// []
9295
unsafe {
@@ -97,7 +100,9 @@ fn test_vmm_mapping() {
97100
false,
98101
),
99102
false,
103+
false,
100104
crate::platform::page_mgmt::FixedAddressBehavior::Replace,
105+
false,
101106
)
102107
}
103108
.unwrap();

litebox_platform_linux_kernel/src/mm/tests.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,11 @@ fn test_vmm_page_fault() {
218218
let p4 = PageTableAllocator::<MockKernel>::allocate_frame(true).unwrap();
219219
let platform = MockKernel::new(p4.start_address());
220220
let litebox = LiteBox::new(platform);
221-
let vmm = PageManager::<_, PAGE_SIZE>::new(&litebox);
221+
let vmm = PageManager::<_, PAGE_SIZE>::new(
222+
&litebox,
223+
<MockKernel as litebox::platform::PageManagementProvider<PAGE_SIZE>>::TASK_ADDR_MIN
224+
..<MockKernel as litebox::platform::PageManagementProvider<PAGE_SIZE>>::TASK_ADDR_MAX,
225+
);
222226
unsafe {
223227
assert_eq!(
224228
vmm.create_writable_pages(

litebox_platform_lvbs/src/mm/tests.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,11 @@ fn test_vmm_page_fault() {
238238
x86_64::PhysAddr::new(0),
239239
);
240240
let litebox = LiteBox::new(platform);
241-
let vmm = PageManager::<_, PAGE_SIZE>::new(&litebox);
241+
let vmm = PageManager::<_, PAGE_SIZE>::new(
242+
&litebox,
243+
<MockKernel as litebox::platform::PageManagementProvider<PAGE_SIZE>>::TASK_ADDR_MIN
244+
..<MockKernel as litebox::platform::PageManagementProvider<PAGE_SIZE>>::TASK_ADDR_MAX,
245+
);
242246
unsafe {
243247
assert_eq!(
244248
vmm.create_writable_pages(

litebox_shim_linux/src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,11 @@ impl LinuxShimBuilder {
191191
net.set_platform_interaction(litebox::net::PlatformInteraction::Manual);
192192
let global = Arc::new(GlobalState {
193193
platform: self.platform,
194-
pm: PageManager::new(&self.litebox),
194+
pm: PageManager::new(
195+
&self.litebox,
196+
<Platform as litebox::platform::PageManagementProvider<{ PAGE_SIZE }>>::TASK_ADDR_MIN
197+
..<Platform as litebox::platform::PageManagementProvider<{ PAGE_SIZE }>>::TASK_ADDR_MAX,
198+
),
195199
futex_manager: FutexManager::new(),
196200
pipes: Pipes::new(&self.litebox),
197201
net: litebox::sync::Mutex::new(net),

litebox_shim_linux/src/syscalls/mm.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ impl<FS: ShimFS> Task<FS> {
238238
true,
239239
fixed_behavior == FixedAddressBehavior::Replace,
240240
flags.contains(MapFlags::MAP_SHARED),
241+
false,
241242
)
242243
}
243244
.unwrap();

litebox_shim_optee/src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,11 @@ impl OpteeShimBuilder {
142142
pub fn build(self) -> OpteeShim {
143143
let global = Arc::new(GlobalState {
144144
platform: self.platform,
145-
pm: PageManager::new(&self.litebox),
145+
pm: PageManager::new(
146+
&self.litebox,
147+
<Platform as litebox::platform::PageManagementProvider<PAGE_SIZE>>::TASK_ADDR_MIN
148+
..<Platform as litebox::platform::PageManagementProvider<PAGE_SIZE>>::TASK_ADDR_MAX,
149+
),
146150
_litebox: self.litebox,
147151
ta_uuid_map: TaUuidMap::new(),
148152
});

0 commit comments

Comments
 (0)