Skip to content

Commit 494ec03

Browse files
committed
feat(balloon): add support for discarding memfd-backed memory
In case the backing is a fd that's mapped shared (eg memfd), we need to fallocate in order to drop the memory page. Signed-off-by: Riccardo Mancini <[email protected]>
1 parent 165522b commit 494ec03

File tree

1 file changed

+74
-3
lines changed

1 file changed

+74
-3
lines changed

src/vmm/src/vstate/memory.rs

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
use std::fs::File;
99
use std::io::SeekFrom;
10+
use std::os::fd::AsRawFd;
1011
use std::sync::Arc;
1112

1213
use kvm_bindings::{KVM_MEM_LOG_DIRTY_PAGES, kvm_userspace_memory_region};
@@ -119,6 +120,7 @@ impl GuestMemoryRegionExt {
119120
// file only drops any anonymous pages in range, but subsequent accesses would read
120121
// whatever page is stored on the backing file. Mmapping anonymous pages ensures
121122
// it's zeroed.
123+
// Punching a hole doesn't work in this case as the file is read-only.
122124
// SAFETY: The address and length are known to be valid.
123125
let ret = unsafe {
124126
libc::mmap(
@@ -138,9 +140,33 @@ impl GuestMemoryRegionExt {
138140
Ok(len)
139141
}
140142
}
141-
// TODO: memfd doesn't actually work with madvise, but this is to keep the
142-
// previous behaviour
143-
(None, _) | (Some(_), _) => {
143+
// If and only if we are using fd-backed memory (eg memfd for vhost-user), we have a
144+
// file descriptor and it's mapped shared.
145+
(Some(file_offset), flags) if flags & libc::MAP_SHARED != 0 => {
146+
// Fallocate to punch a hole in the region to free memory pages.
147+
// In case of a FD mmapped as shared (eg memfd), MADV_DONTNEED doesn't work
148+
// and we can't just mmap a zero page on top of it as that doesn't actually
149+
// change the underlying shared FD, but we can punch a hole into it.
150+
// SAFETY: The address and length are known to be valid.
151+
let ret = unsafe {
152+
libc::fallocate64(
153+
file_offset.file().as_raw_fd(),
154+
libc::FALLOC_FL_PUNCH_HOLE | libc::FALLOC_FL_KEEP_SIZE,
155+
i64::try_from(file_offset.start() + caddr.0).unwrap(),
156+
len.try_into().unwrap(),
157+
)
158+
};
159+
if ret < 0 {
160+
let os_error = std::io::Error::last_os_error();
161+
error!("discard_range: fallocate64 failed: {:?}", os_error);
162+
Err(GuestMemoryError::IOError(os_error))
163+
} else {
164+
Ok(len)
165+
}
166+
}
167+
// Either it's mapped PRIVATE or SHARED
168+
(Some(_), _) => unreachable!(),
169+
(None, _) => {
144170
// Madvise the region in order to mark it as not used.
145171
// SAFETY: The address and length are known to be valid.
146172
let ret = unsafe { libc::madvise(phys_address.cast(), len, libc::MADV_DONTNEED) };
@@ -1044,4 +1070,49 @@ mod tests {
10441070
GuestMemoryError::IOError(_)
10451071
);
10461072
}
1073+
1074+
#[test]
1075+
fn test_discard_range_on_memfd() {
1076+
let page_size: usize = 0x1000;
1077+
let mem = into_region_ext(
1078+
memfd_backed(
1079+
&[(GuestAddress(0), 2 * page_size)],
1080+
false,
1081+
HugePageConfig::None,
1082+
)
1083+
.unwrap(),
1084+
);
1085+
1086+
// Fill the memory with ones.
1087+
let ones = vec![1u8; 2 * page_size];
1088+
mem.write(&ones[..], GuestAddress(0)).unwrap();
1089+
1090+
// Remove the first page.
1091+
mem.discard_range(GuestAddress(0), page_size).unwrap();
1092+
1093+
// Check that the first page is zeroed.
1094+
let mut actual_page = vec![0u8; page_size];
1095+
mem.read(actual_page.as_mut_slice(), GuestAddress(0))
1096+
.unwrap();
1097+
assert_eq!(vec![0u8; page_size], actual_page);
1098+
// Check that the second page still contains ones.
1099+
mem.read(actual_page.as_mut_slice(), GuestAddress(page_size as u64))
1100+
.unwrap();
1101+
assert_eq!(vec![1u8; page_size], actual_page);
1102+
1103+
// Malformed range: the len is too big.
1104+
assert_match!(
1105+
mem.discard_range(GuestAddress(0), 0x10000).unwrap_err(),
1106+
GuestMemoryError::PartialBuffer {
1107+
expected: _,
1108+
completed: _
1109+
}
1110+
);
1111+
1112+
// Region not mapped.
1113+
assert_match!(
1114+
mem.discard_range(GuestAddress(0x10000), 0x10).unwrap_err(),
1115+
GuestMemoryError::InvalidGuestAddress(_)
1116+
);
1117+
}
10471118
}

0 commit comments

Comments
 (0)