Skip to content

Commit

Permalink
feat(memfd): added memfd backed guest memory
Browse files Browse the repository at this point in the history
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 <yegorlz@amazon.co.uk>
  • Loading branch information
ShadowCurse committed Oct 17, 2023
1 parent ac174c5 commit 027a992
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 10 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/vmm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ kvm-ioctls = "0.15.0"
lazy_static = "1.4.0"
libc = "0.2.117"
linux-loader = "0.9.0"
memfd = "0.6.3"
serde = { version = "1.0.136", features = ["derive", "rc"] }
semver = { version = "1.0.17", features = ["serde"] }
serde_json = "1.0.78"
Expand Down
7 changes: 4 additions & 3 deletions src/vmm/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,9 +265,10 @@ pub fn build_microvm_for_boot(
.ok_or(MissingKernelConfig)?;

let track_dirty_pages = vm_resources.track_dirty_pages();
let guest_memory =
GuestMemoryMmap::with_size(vm_resources.vm_config.mem_size_mib, track_dirty_pages)
.map_err(StartMicrovmError::GuestMemory)?;
let memfd = crate::vstate::memory::create_memfd(vm_resources.vm_config.mem_size_mib)
.map_err(StartMicrovmError::GuestMemory)?;
let guest_memory = GuestMemoryMmap::with_file(memfd.as_file(), track_dirty_pages)
.map_err(StartMicrovmError::GuestMemory)?;
let entry_addr = load_kernel(boot_config, &guest_memory)?;
let initrd = load_initrd_from_config(boot_config, &guest_memory)?;
// Clone the command-line so that a failed boot doesn't pollute the original.
Expand Down
89 changes: 84 additions & 5 deletions src/vmm/src/vstate/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const GUARD_PAGE_COUNT: usize = 1;
#[derive(Debug, thiserror::Error, displaydoc::Display)]
pub enum MemoryError {
/// Cannot access file: {0:?}
FileHandle(std::io::Error),
FileError(std::io::Error),
/// Cannot create memory: {0:?}
CreateMemory(VmMemoryError),
/// Cannot create memory region: {0:?}
Expand All @@ -50,13 +50,20 @@ pub enum MemoryError {
MmapRegionError(MmapRegionError),
/// Cannot create guest memory: {0}
VmMemoryError(VmMemoryError),
/// Cannot create memfd: {0:?}
Memfd(memfd::Error),
/// Cannot resize memfd file: {0:?}
MemfdSetLen(std::io::Error),
}

/// Defines the interface for snapshotting memory.
pub trait GuestMemoryExtension
where
Self: Sized,
{
/// Creates a GuestMemoryMmap with `size` in MiB and guard pages backed by file.
fn with_file(file: &File, track_dirty_pages: bool) -> Result<Self, MemoryError>;

/// Creates a GuestMemoryMmap with `size` in MiB and guard pages.
fn with_size(size: usize, track_dirty_pages: bool) -> Result<Self, MemoryError>;

Expand Down Expand Up @@ -119,15 +126,45 @@ pub struct GuestMemoryState {
}

impl GuestMemoryExtension for GuestMemoryMmap {
/// Creates a GuestMemoryMmap with `size` in MiB and guard pages.
/// Creates a GuestMemoryMmap with `size` in MiB and guard pages backed by file.
fn with_file(file: &File, track_dirty_pages: bool) -> Result<Self, MemoryError> {
let metadata = file.metadata().map_err(MemoryError::FileError)?;
let mem_size = u64_to_usize(metadata.len());
let regions = crate::arch::arch_memory_regions(mem_size);

let prot = libc::PROT_READ | libc::PROT_WRITE;
let flags = libc::MAP_NORESERVE | libc::MAP_SHARED;

let mut offset: u64 = 0;
let regions = regions
.iter()
.map(|(guest_address, region_size)| {
let file_clone = file.try_clone().map_err(MemoryError::FileError)?;
let file_offset = FileOffset::new(file_clone, offset);
offset += *region_size as u64;
let region = build_guarded_region(
Some(&file_offset),
*region_size,
prot,
flags,
track_dirty_pages,
)?;
GuestRegionMmap::new(region, *guest_address).map_err(MemoryError::VmMemoryError)
})
.collect::<Result<Vec<_>, MemoryError>>()?;

GuestMemoryMmap::from_regions(regions).map_err(MemoryError::VmMemoryError)
}

/// Creates a GuestMemoryMmap with `size` in MiB and guard pages backed by anonymous memory.
fn with_size(size: usize, track_dirty_pages: bool) -> Result<Self, MemoryError> {
let mem_size = size << 20;
let regions = crate::arch::arch_memory_regions(mem_size);

Self::from_raw_regions(&regions, track_dirty_pages)
}

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

/// Creates a GuestMemoryMmap from raw regions with no guard pages.
/// Creates a GuestMemoryMmap from raw regions with no guard pages backed by anonymous memory.
fn from_raw_regions_unguarded(
regions: &[(GuestAddress, usize)],
track_dirty_pages: bool,
Expand Down Expand Up @@ -195,7 +232,7 @@ impl GuestMemoryExtension for GuestMemoryMmap {
})
})
.collect::<Result<Vec<_>, std::io::Error>>()
.map_err(MemoryError::FileHandle)?;
.map_err(MemoryError::FileError)?;

let prot = libc::PROT_READ | libc::PROT_WRITE;
let flags = libc::MAP_NORESERVE | libc::MAP_PRIVATE;
Expand Down Expand Up @@ -322,6 +359,33 @@ impl GuestMemoryExtension for GuestMemoryMmap {
}
}

/// Creates a memfd file with the `size` in MiB.
pub fn create_memfd(size: usize) -> Result<memfd::Memfd, MemoryError> {
let mem_size = size << 20;
// Create a memfd.
let opts = memfd::MemfdOptions::default().allow_sealing(true);
let mem_file = opts.create("guest_mem").map_err(MemoryError::Memfd)?;

// Resize to guest mem size.
mem_file
.as_file()
.set_len(mem_size as u64)
.map_err(MemoryError::MemfdSetLen)?;

// Add seals to prevent further resizing.
let mut seals = memfd::SealsHashSet::new();
seals.insert(memfd::FileSeal::SealShrink);
seals.insert(memfd::FileSeal::SealGrow);
mem_file.add_seals(&seals).map_err(MemoryError::Memfd)?;

// Prevent further sealing changes.
mem_file
.add_seal(memfd::FileSeal::SealSeal)
.map_err(MemoryError::Memfd)?;

Ok(mem_file)
}

/// Build a `MmapRegion` surrounded by guard pages.
///
/// Initially, we map a `PROT_NONE` guard region of size:
Expand Down Expand Up @@ -844,4 +908,19 @@ mod tests {
assert_eq!(expected_first_region, diff_file_content);
}
}

#[test]
fn test_create_memfd() {
let size = 1;
let size_mb = 1 << 20;

let memfd = create_memfd(size).unwrap();

assert_eq!(memfd.as_file().metadata().unwrap().len(), size_mb);
assert!(memfd.as_file().set_len(0x69).is_err());

let mut seals = memfd::SealsHashSet::new();
seals.insert(memfd::FileSeal::SealGrow);
assert!(memfd.add_seals(&seals).is_err());
}
}
3 changes: 1 addition & 2 deletions tests/integration_tests/functional/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,8 +389,7 @@ def test_api_machine_config(test_microvm_with_api):
test_microvm.api.machine_config.patch(mem_size_mib=bad_size)

fail_msg = re.escape(
"Invalid Memory Configuration: MmapRegion(Mmap(Os { code: "
"12, kind: OutOfMemory, message: Out of memory }))"
"Invalid Memory Configuration: MemfdSetLen(Custom { kind: InvalidInput, error: TryFromIntError(()) })"
)
with pytest.raises(RuntimeError, match=fail_msg):
test_microvm.start()
Expand Down

0 comments on commit 027a992

Please sign in to comment.