diff --git a/docs/memory.md b/docs/memory.md index 17f0b91859..97a8e5b1ad 100644 --- a/docs/memory.md +++ b/docs/memory.md @@ -221,6 +221,9 @@ there will be fewer virtualisation related page faults. Unlike using `hugepages=on` a specific number of huge pages do not need to be allocated by the kernel. +A restore with `memory_restore_mode=copyonwrite` does not apply this option to +the restored memory. + By default this option is turned on. _Example_ diff --git a/docs/snapshot_restore.md b/docs/snapshot_restore.md index a5558854c2..e6e6920f4d 100644 --- a/docs/snapshot_restore.md +++ b/docs/snapshot_restore.md @@ -211,8 +211,15 @@ Current constraints for `memory_restore_mode=copyonwrite`: `id`+`size` zone is fine), resizable RAM (`hotplug_size`, virtio-mem), KSM (`mergeable=on`), `--pvmemcontrol`, device passthrough (`--device`/`--user-device`/`--vdpa`), and snapshot ranges that are not - page-aligned single-region extents. Each region's `reserve` policy and the - THP policy are re-applied to the mapped region. + page-aligned single-region extents. Each region's `reserve` policy is + re-applied to the mapped region. THP is not. A page the guest writes becomes + a private anonymous copy of one host base page (4 KiB on x86_64) either way. + On a filesystem with large folios, `MADV_HUGEPAGE` lets read faults map + PMD-sized file folios (2 MiB on x86_64), which speeds up random reads of + read-mostly guest memory (by about 3.5% in one measurement). But a read fault + that misses the page cache can then request readahead of two PMD-sized + folios, about 4 MiB, which made the restore about 1.5 times slower on an idle + host and about 5 times slower with 16 VMs restoring at once. - A snapshot memory file shorter than the saved ranges is rejected up front (it would otherwise fault `SIGBUS` at run time). - `prefault` is rejected. diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index d06ce097d1..afb0a0000b 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -1050,7 +1050,7 @@ impl MemoryManager { "snapshot memory file is shorter than the saved ranges", ))); } - do_mmap_cow_saved_regions(&guest_memory, &memory_file, saved_regions, self.thp)?; + do_mmap_cow_saved_regions(&guest_memory, &memory_file, saved_regions)?; // Landlock applies after the restore, so the pagemap is opened here. self.cow_source = match PageMap::open() { Ok(pagemap) => Some(CowSource { @@ -4157,13 +4157,11 @@ fn is_restore_cow_compatible(guest_memory: &GuestMemory, saved_regions: &MemoryR } // Maps each saved range over its guest RAM window, re-applying the owning -// region's reserve policy and the THP policy. Ranges must have passed -// `is_restore_cow_compatible`. +// region's reserve policy. Ranges must have passed `is_restore_cow_compatible`. fn do_mmap_cow_saved_regions( guest_memory: &GuestMemory, memory_file: &File, saved_regions: &MemoryRangeTable, - thp: bool, ) -> Result<(), Error> { let mut file_offset: u64 = 0; for range in saved_regions.ranges() { @@ -4196,16 +4194,6 @@ fn do_mmap_cow_saved_regions( if ret == libc::MAP_FAILED { return Err(Error::SnapshotMmap(io::Error::last_os_error())); } - if thp { - // SAFETY: ret/length name the private mapping just installed above. - let adv = unsafe { libc::madvise(ret, length, libc::MADV_HUGEPAGE) }; - if adv != 0 { - warn!( - "Restore (mode=copyonwrite): MADV_HUGEPAGE failed: {}", - io::Error::last_os_error() - ); - } - } file_offset = file_offset.checked_add(range.length).ok_or_else(|| { Error::SnapshotMmap(io::Error::other("snapshot range file offset overflow")) })?; @@ -4453,7 +4441,7 @@ mod tests { }); assert!(is_restore_cow_compatible(&gm, &table)); - do_mmap_cow_saved_regions(&gm, &file, &table, false).unwrap(); + do_mmap_cow_saved_regions(&gm, &file, &table).unwrap(); assert_eq!(gm.read_obj::(GuestAddress(0)).unwrap(), 0xab); assert_eq!(gm.read_obj::(GuestAddress(page - 1)).unwrap(), 0xab); @@ -4481,7 +4469,7 @@ mod tests { gpa: 0, length: 16 * page, }); - do_mmap_cow_saved_regions(&gm, &file, &ranges, false).unwrap(); + do_mmap_cow_saved_regions(&gm, &file, &ranges).unwrap(); let source = CowSource { file, ranges, @@ -4551,7 +4539,7 @@ mod tests { } #[test] - fn cow_restore_honors_region_reserve_and_thp() { + fn cow_restore_honors_region_reserve() { let page = get_page_size(); let mut file = tempfile::tempfile().unwrap(); file.write_all(&vec![0xcdu8; page as usize]).unwrap(); @@ -4573,7 +4561,7 @@ mod tests { gpa: 0, length: page, }); - do_mmap_cow_saved_regions(&gm, &file, &table, true).unwrap(); + do_mmap_cow_saved_regions(&gm, &file, &table).unwrap(); assert_eq!(gm.read_obj::(GuestAddress(0)).unwrap(), 0xcd); } }