Skip to content

Commit 027a992

Browse files
committed
feat(memfd): added memfd backed guest memory
Added method to create `memfd` file with needed size and seals. Added an ability to construct `GuestMemoryMmap` backed by a file. Changed expected error message for failed memory creation in `test_api` test Signed-off-by: Egor Lazarchuk <[email protected]>
1 parent ac174c5 commit 027a992

File tree

5 files changed

+100
-10
lines changed

5 files changed

+100
-10
lines changed

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/vmm/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ kvm-ioctls = "0.15.0"
1818
lazy_static = "1.4.0"
1919
libc = "0.2.117"
2020
linux-loader = "0.9.0"
21+
memfd = "0.6.3"
2122
serde = { version = "1.0.136", features = ["derive", "rc"] }
2223
semver = { version = "1.0.17", features = ["serde"] }
2324
serde_json = "1.0.78"

src/vmm/src/builder.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -265,9 +265,10 @@ pub fn build_microvm_for_boot(
265265
.ok_or(MissingKernelConfig)?;
266266

267267
let track_dirty_pages = vm_resources.track_dirty_pages();
268-
let guest_memory =
269-
GuestMemoryMmap::with_size(vm_resources.vm_config.mem_size_mib, track_dirty_pages)
270-
.map_err(StartMicrovmError::GuestMemory)?;
268+
let memfd = crate::vstate::memory::create_memfd(vm_resources.vm_config.mem_size_mib)
269+
.map_err(StartMicrovmError::GuestMemory)?;
270+
let guest_memory = GuestMemoryMmap::with_file(memfd.as_file(), track_dirty_pages)
271+
.map_err(StartMicrovmError::GuestMemory)?;
271272
let entry_addr = load_kernel(boot_config, &guest_memory)?;
272273
let initrd = load_initrd_from_config(boot_config, &guest_memory)?;
273274
// Clone the command-line so that a failed boot doesn't pollute the original.

src/vmm/src/vstate/memory.rs

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const GUARD_PAGE_COUNT: usize = 1;
3737
#[derive(Debug, thiserror::Error, displaydoc::Display)]
3838
pub enum MemoryError {
3939
/// Cannot access file: {0:?}
40-
FileHandle(std::io::Error),
40+
FileError(std::io::Error),
4141
/// Cannot create memory: {0:?}
4242
CreateMemory(VmMemoryError),
4343
/// Cannot create memory region: {0:?}
@@ -50,13 +50,20 @@ pub enum MemoryError {
5050
MmapRegionError(MmapRegionError),
5151
/// Cannot create guest memory: {0}
5252
VmMemoryError(VmMemoryError),
53+
/// Cannot create memfd: {0:?}
54+
Memfd(memfd::Error),
55+
/// Cannot resize memfd file: {0:?}
56+
MemfdSetLen(std::io::Error),
5357
}
5458

5559
/// Defines the interface for snapshotting memory.
5660
pub trait GuestMemoryExtension
5761
where
5862
Self: Sized,
5963
{
64+
/// Creates a GuestMemoryMmap with `size` in MiB and guard pages backed by file.
65+
fn with_file(file: &File, track_dirty_pages: bool) -> Result<Self, MemoryError>;
66+
6067
/// Creates a GuestMemoryMmap with `size` in MiB and guard pages.
6168
fn with_size(size: usize, track_dirty_pages: bool) -> Result<Self, MemoryError>;
6269

@@ -119,15 +126,45 @@ pub struct GuestMemoryState {
119126
}
120127

121128
impl GuestMemoryExtension for GuestMemoryMmap {
122-
/// Creates a GuestMemoryMmap with `size` in MiB and guard pages.
129+
/// Creates a GuestMemoryMmap with `size` in MiB and guard pages backed by file.
130+
fn with_file(file: &File, track_dirty_pages: bool) -> Result<Self, MemoryError> {
131+
let metadata = file.metadata().map_err(MemoryError::FileError)?;
132+
let mem_size = u64_to_usize(metadata.len());
133+
let regions = crate::arch::arch_memory_regions(mem_size);
134+
135+
let prot = libc::PROT_READ | libc::PROT_WRITE;
136+
let flags = libc::MAP_NORESERVE | libc::MAP_SHARED;
137+
138+
let mut offset: u64 = 0;
139+
let regions = regions
140+
.iter()
141+
.map(|(guest_address, region_size)| {
142+
let file_clone = file.try_clone().map_err(MemoryError::FileError)?;
143+
let file_offset = FileOffset::new(file_clone, offset);
144+
offset += *region_size as u64;
145+
let region = build_guarded_region(
146+
Some(&file_offset),
147+
*region_size,
148+
prot,
149+
flags,
150+
track_dirty_pages,
151+
)?;
152+
GuestRegionMmap::new(region, *guest_address).map_err(MemoryError::VmMemoryError)
153+
})
154+
.collect::<Result<Vec<_>, MemoryError>>()?;
155+
156+
GuestMemoryMmap::from_regions(regions).map_err(MemoryError::VmMemoryError)
157+
}
158+
159+
/// Creates a GuestMemoryMmap with `size` in MiB and guard pages backed by anonymous memory.
123160
fn with_size(size: usize, track_dirty_pages: bool) -> Result<Self, MemoryError> {
124161
let mem_size = size << 20;
125162
let regions = crate::arch::arch_memory_regions(mem_size);
126163

127164
Self::from_raw_regions(&regions, track_dirty_pages)
128165
}
129166

130-
/// Creates a GuestMemoryMmap from raw regions with guard pages.
167+
/// Creates a GuestMemoryMmap from raw regions with guard pages backed by anonymous memory.
131168
fn from_raw_regions(
132169
regions: &[(GuestAddress, usize)],
133170
track_dirty_pages: bool,
@@ -147,7 +184,7 @@ impl GuestMemoryExtension for GuestMemoryMmap {
147184
GuestMemoryMmap::from_regions(regions).map_err(MemoryError::VmMemoryError)
148185
}
149186

150-
/// Creates a GuestMemoryMmap from raw regions with no guard pages.
187+
/// Creates a GuestMemoryMmap from raw regions with no guard pages backed by anonymous memory.
151188
fn from_raw_regions_unguarded(
152189
regions: &[(GuestAddress, usize)],
153190
track_dirty_pages: bool,
@@ -195,7 +232,7 @@ impl GuestMemoryExtension for GuestMemoryMmap {
195232
})
196233
})
197234
.collect::<Result<Vec<_>, std::io::Error>>()
198-
.map_err(MemoryError::FileHandle)?;
235+
.map_err(MemoryError::FileError)?;
199236

200237
let prot = libc::PROT_READ | libc::PROT_WRITE;
201238
let flags = libc::MAP_NORESERVE | libc::MAP_PRIVATE;
@@ -322,6 +359,33 @@ impl GuestMemoryExtension for GuestMemoryMmap {
322359
}
323360
}
324361

362+
/// Creates a memfd file with the `size` in MiB.
363+
pub fn create_memfd(size: usize) -> Result<memfd::Memfd, MemoryError> {
364+
let mem_size = size << 20;
365+
// Create a memfd.
366+
let opts = memfd::MemfdOptions::default().allow_sealing(true);
367+
let mem_file = opts.create("guest_mem").map_err(MemoryError::Memfd)?;
368+
369+
// Resize to guest mem size.
370+
mem_file
371+
.as_file()
372+
.set_len(mem_size as u64)
373+
.map_err(MemoryError::MemfdSetLen)?;
374+
375+
// Add seals to prevent further resizing.
376+
let mut seals = memfd::SealsHashSet::new();
377+
seals.insert(memfd::FileSeal::SealShrink);
378+
seals.insert(memfd::FileSeal::SealGrow);
379+
mem_file.add_seals(&seals).map_err(MemoryError::Memfd)?;
380+
381+
// Prevent further sealing changes.
382+
mem_file
383+
.add_seal(memfd::FileSeal::SealSeal)
384+
.map_err(MemoryError::Memfd)?;
385+
386+
Ok(mem_file)
387+
}
388+
325389
/// Build a `MmapRegion` surrounded by guard pages.
326390
///
327391
/// Initially, we map a `PROT_NONE` guard region of size:
@@ -844,4 +908,19 @@ mod tests {
844908
assert_eq!(expected_first_region, diff_file_content);
845909
}
846910
}
911+
912+
#[test]
913+
fn test_create_memfd() {
914+
let size = 1;
915+
let size_mb = 1 << 20;
916+
917+
let memfd = create_memfd(size).unwrap();
918+
919+
assert_eq!(memfd.as_file().metadata().unwrap().len(), size_mb);
920+
assert!(memfd.as_file().set_len(0x69).is_err());
921+
922+
let mut seals = memfd::SealsHashSet::new();
923+
seals.insert(memfd::FileSeal::SealGrow);
924+
assert!(memfd.add_seals(&seals).is_err());
925+
}
847926
}

tests/integration_tests/functional/test_api.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,8 +389,7 @@ def test_api_machine_config(test_microvm_with_api):
389389
test_microvm.api.machine_config.patch(mem_size_mib=bad_size)
390390

391391
fail_msg = re.escape(
392-
"Invalid Memory Configuration: MmapRegion(Mmap(Os { code: "
393-
"12, kind: OutOfMemory, message: Out of memory }))"
392+
"Invalid Memory Configuration: MemfdSetLen(Custom { kind: InvalidInput, error: TryFromIntError(()) })"
394393
)
395394
with pytest.raises(RuntimeError, match=fail_msg):
396395
test_microvm.start()

0 commit comments

Comments
 (0)