Skip to content

Commit 6d5bfac

Browse files
committed
libkernel: vmarea: shrink_to: implement
Implement a `shrink_to` function for a VMA. This shrinks the VMA's region to the specified region, while recalculating file offsets and size if the VMA is file-backed.
1 parent f57a6db commit 6d5bfac

File tree

1 file changed

+183
-0
lines changed

1 file changed

+183
-0
lines changed

libkernel/src/memory/proc_vm/vmarea.rs

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
//! and initialized data from files, most notably ELF binaries.
1010
//! - Anonymous (via [`VMAreaKind::Anon`]): Used for demand-zeroed memory like
1111
//! the process stack, heap, and BSS sections.
12+
use core::cmp;
13+
1214
use crate::{
1315
fs::Inode,
1416
memory::{PAGE_MASK, PAGE_SIZE, address::VA, region::VirtMemoryRegion},
@@ -408,6 +410,42 @@ impl VMArea {
408410
pub fn is_file_backed(&self) -> bool {
409411
matches!(self.kind, VMAreaKind::File(_))
410412
}
413+
414+
/// Shrink this VMA's region to `new_region`, recalculating file offsets,
415+
/// for file mappings.
416+
#[must_use]
417+
pub(crate) fn shrink_to(&self, new_region: VirtMemoryRegion) -> Self {
418+
debug_assert!(self.region.contains(new_region));
419+
420+
let mut new_vma = self.clone_with_new_region(new_region);
421+
422+
match self.kind {
423+
VMAreaKind::File(ref vmfile_mapping) => {
424+
let start_offset =
425+
new_region.start_address().value() - self.region.start_address().value();
426+
427+
let new_sz = cmp::min(
428+
vmfile_mapping.len.saturating_sub(start_offset as u64),
429+
new_region.size() as u64,
430+
);
431+
432+
if new_sz == 0 {
433+
// convert this VMA into an anonymous VMA, since we've
434+
// shrunk past the file mapping.
435+
new_vma.kind = VMAreaKind::Anon;
436+
} else {
437+
new_vma.kind = VMAreaKind::File(VMFileMapping {
438+
file: vmfile_mapping.file.clone(),
439+
offset: vmfile_mapping.offset + start_offset as u64,
440+
len: new_sz,
441+
});
442+
}
443+
444+
new_vma
445+
}
446+
VMAreaKind::Anon => new_vma,
447+
}
448+
}
411449
}
412450

413451
#[cfg(test)]
@@ -569,4 +607,149 @@ pub mod tests {
569607
let result = vma.resolve_fault(fault_addr);
570608
assert!(result.is_none(), "Anonymous VMA fault should return None");
571609
}
610+
611+
#[test]
612+
fn shrink_anonymous_vma() {
613+
// Easy case: Standard shrinking of an anonymous region
614+
let vma = VMArea::new(
615+
VirtMemoryRegion::new(VA::from_value(0x5000), 0x4000),
616+
VMAreaKind::Anon,
617+
VMAPermissions::rw(),
618+
);
619+
620+
// Shrink to the middle.
621+
let new_region = VirtMemoryRegion::new(VA::from_value(0x6000), 0x1000);
622+
let result = vma.shrink_to(new_region);
623+
624+
assert_eq!(result.region, new_region);
625+
assert!(matches!(result.kind, VMAreaKind::Anon));
626+
}
627+
628+
#[test]
629+
fn shrink_file_vma_from_front() {
630+
// Scenario: [ File (0x4000) ]
631+
// Cut: xx[ File (0x1000) ]
632+
// Expect: Offset increases, Length decreases
633+
634+
let vma = create_test_vma(0x1000, 0x4000, 0x0, 0x4000);
635+
let new_region = VirtMemoryRegion::new(VA::from_value(0x4000), 0x1000);
636+
637+
let result = vma.shrink_to(new_region);
638+
639+
assert_eq!(result.region, new_region);
640+
match result.kind {
641+
VMAreaKind::File(vmfile_mapping) => {
642+
assert_eq!(vmfile_mapping.len, 0x1000);
643+
assert_eq!(vmfile_mapping.offset, 0x3000);
644+
}
645+
_ => panic!("Expected File VMA"),
646+
}
647+
}
648+
649+
#[test]
650+
fn shrink_file_vma_from_end() {
651+
// Scenario: [ File (0x4000) ]
652+
// Cut: [ File (0x2000) ]xx
653+
654+
let vma = create_test_vma(0x1000, 0x4000, 0x0, 0x4000);
655+
let new_region = VirtMemoryRegion::new(VA::from_value(0x1000), 0x2000);
656+
657+
let result = vma.shrink_to(new_region);
658+
659+
assert_eq!(result.region, new_region);
660+
661+
match result.kind {
662+
VMAreaKind::File(vmfile_mapping) => {
663+
// Offset shouldn't change
664+
assert_eq!(vmfile_mapping.offset, 0x0);
665+
assert_eq!(vmfile_mapping.len, 0x2000);
666+
}
667+
_ => panic!("Expected File VMA"),
668+
}
669+
}
670+
671+
#[test]
672+
fn shrink_file_vma_both_sides() {
673+
// Scenario: [ File (0x4000) ]
674+
// Cut: xx[ File (0x1000) ]xx
675+
let vma = create_test_vma(0x1000, 0x4000, 0x0, 0x4000);
676+
let new_region = VirtMemoryRegion::new(VA::from_value(0x2000), 0x1000);
677+
678+
let result = vma.shrink_to(new_region);
679+
680+
match result.kind {
681+
VMAreaKind::File(vmfile_mapping) => {
682+
assert_eq!(vmfile_mapping.offset, 0x1000);
683+
assert_eq!(vmfile_mapping.len, 0x1000);
684+
}
685+
_ => panic!("Expected File VMA"),
686+
}
687+
}
688+
689+
#[test]
690+
fn shrink_mixed_vma_past_file_boundary_becomes_anon() {
691+
// Scenario: [ File (0x2000) | Anon (0x2000) ]
692+
// Cut front by 0x3000. xx[ Anon (0x1000) ]
693+
694+
let vma = create_test_vma(0x1000, 0x4000, 0x0, 0x2000);
695+
696+
let new_region = VirtMemoryRegion::new(VA::from_value(0x4000), 0x1000);
697+
698+
let result = vma.shrink_to(new_region);
699+
700+
assert_eq!(result.region, new_region);
701+
assert!(
702+
matches!(result.kind, VMAreaKind::Anon),
703+
"Should have converted to Anon because we skipped the file part"
704+
);
705+
}
706+
707+
#[test]
708+
fn shrink_mixed_vma_keep_file_part() {
709+
// Scenario: [ File (0x2000) | Anon (0x2000) ]
710+
// cut: xx[ File(0x1000)| Anon (0x2000) ]
711+
let vma = create_test_vma(0x1000, 0x4000, 0x0, 0x2000);
712+
let new_region = VirtMemoryRegion::new(VA::from_value(0x2000), 0x3000);
713+
714+
let result = vma.shrink_to(new_region);
715+
716+
match result.kind {
717+
VMAreaKind::File(mapping) => {
718+
assert_eq!(mapping.offset, 0x1000);
719+
assert_eq!(mapping.len, 0x1000);
720+
}
721+
_ => panic!("Expected File VMA"),
722+
}
723+
}
724+
725+
#[test]
726+
fn shrink_mixed_vma_from_end_remove_anon_part() {
727+
// Scenario: [ File (0x2000) | Anon (0x2000) ]
728+
// Cut: [ File (0x2000) ]xx
729+
let vma = create_test_vma(0x1000, 0x4000, 0x0, 0x2000);
730+
let new_region = VirtMemoryRegion::new(VA::from_value(0x1000), 0x2000);
731+
732+
let result = vma.shrink_to(new_region);
733+
734+
match result.kind {
735+
VMAreaKind::File(mapping) => {
736+
assert_eq!(mapping.offset, 0x0);
737+
assert_eq!(mapping.len, 0x2000);
738+
}
739+
_ => panic!("Expected File VMA"),
740+
}
741+
}
742+
743+
#[test]
744+
fn shrink_mixed_vma_exact_boundary() {
745+
// Scenario: [ File (0x2000) | Anon (0x2000) ]
746+
// Cut: xxx[ Anon (0x2000) ]
747+
748+
let vma = create_test_vma(0x1000, 0x4000, 0x0, 0x2000);
749+
let new_region = VirtMemoryRegion::new(VA::from_value(0x3000), 0x1000);
750+
751+
let result = vma.shrink_to(new_region);
752+
753+
assert!(matches!(result.kind, VMAreaKind::Anon));
754+
}
572755
}

0 commit comments

Comments
 (0)