Skip to content

Commit c283e22

Browse files
ValentaTomasbchalios
authored andcommitted
fix(memory): punch holes for shared discard ranges
Use fallocate(PUNCH_HOLE|KEEP_SIZE) for MAP_SHARED file-backed guest memory so memfd-backed balloon hinting/reporting clears the shared backing instead of only dropping PTEs. Signed-off-by: Babis Chalios <babis.chalios@e2b.dev>
1 parent 639196c commit c283e22

1 file changed

Lines changed: 53 additions & 10 deletions

File tree

src/vmm/src/vstate/memory.rs

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ pub use vm_memory::{
2222
GuestUsize, MemoryRegionAddress, MmapRegion, address,
2323
};
2424
use vm_memory::{GuestMemoryError, GuestMemoryRegionBytes, VolatileSlice, WriteVolatile};
25-
use vmm_sys_util::errno;
25+
use vmm_sys_util::fallocate::FallocateMode;
26+
use vmm_sys_util::{errno, fallocate};
2627

27-
use crate::utils::{get_page_size, u64_to_usize};
28+
use crate::utils::{get_page_size, u64_to_usize, usize_to_u64};
2829
use crate::vmm_config::machine_config::HugePageConfig;
2930
use crate::vstate::vm::VmError;
3031
use crate::{DirtyBitmap, Vm};
@@ -391,8 +392,7 @@ impl GuestRegionMmapExt {
391392
let phys_address = self.get_host_address(caddr)?;
392393

393394
match (self.inner.file_offset(), self.inner.flags()) {
394-
// If and only if we are resuming from a snapshot file, we have a file and it's mapped
395-
// private
395+
// If we are resuming from a snapshot file, we have a file and it's mapped private
396396
(Some(_), flags) if flags & libc::MAP_PRIVATE != 0 => {
397397
// Mmap a new anonymous region over the present one in order to create a hole
398398
// with zero pages.
@@ -420,12 +420,29 @@ impl GuestRegionMmapExt {
420420
Ok(())
421421
}
422422
}
423-
// Match either the case of an anonymous mapping, or the case
424-
// of a shared file mapping.
425-
// TODO: madvise(MADV_DONTNEED) doesn't actually work with memfd
426-
// (or in general MAP_SHARED of a fd). In those cases we should use
427-
// fallocate64(FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE).
428-
// We keep falling to the madvise branch to keep the previous behaviour.
423+
// If we back memory over memfd we have a file mapped shared.
424+
(Some(file_offset), flags) if flags & libc::MAP_SHARED != 0 => {
425+
let Some(offset) = file_offset.start().checked_add(caddr.raw_value()) else {
426+
return Err(GuestMemoryError::InvalidGuestAddress(GuestAddress(
427+
caddr.raw_value(),
428+
)));
429+
};
430+
431+
fallocate::fallocate(
432+
file_offset.file(),
433+
FallocateMode::PunchHole,
434+
true,
435+
offset,
436+
usize_to_u64(len),
437+
)
438+
.map_err(|err| {
439+
error!("discard_range: punching hole failed: {err:?}");
440+
GuestMemoryError::IOError(err.into())
441+
})?;
442+
443+
Ok(())
444+
}
445+
// Anonymous mapping.
429446
_ => {
430447
// Madvise the region in order to mark it as not used.
431448
// SAFETY: The address and length are known to be valid.
@@ -1462,6 +1479,32 @@ mod tests {
14621479
);
14631480
}
14641481

1482+
#[test]
1483+
fn test_discard_range_on_shared_memfd() {
1484+
let page_size: usize = 0x1000;
1485+
let (mem, _file) = memfd_backed(
1486+
&[(GuestAddress(0), 2 * page_size)],
1487+
false,
1488+
HugePageConfig::None,
1489+
)
1490+
.unwrap();
1491+
let mem = into_region_ext(mem);
1492+
1493+
let ones = vec![1u8; 2 * page_size];
1494+
mem.write(&ones, GuestAddress(0)).unwrap();
1495+
1496+
mem.discard_range(GuestAddress(0), page_size).unwrap();
1497+
1498+
let mut actual_page = vec![0u8; page_size];
1499+
mem.read(actual_page.as_mut_slice(), GuestAddress(0))
1500+
.unwrap();
1501+
assert_eq!(vec![0u8; page_size], actual_page);
1502+
1503+
mem.read(actual_page.as_mut_slice(), GuestAddress(page_size as u64))
1504+
.unwrap();
1505+
assert_eq!(vec![1u8; page_size], actual_page);
1506+
}
1507+
14651508
/// Verifies that `slots_intersecting_range` returns the correct slots for
14661509
/// ranges at slot boundaries, interior to a slot, and spanning two slots.
14671510
#[test]

0 commit comments

Comments
 (0)