diff --git a/cloud-hypervisor/src/main.rs b/cloud-hypervisor/src/main.rs index c0d5fd599..69cc9ec60 100644 --- a/cloud-hypervisor/src/main.rs +++ b/cloud-hypervisor/src/main.rs @@ -321,7 +321,7 @@ fn get_cli_options_sorted( hotplug_method=acpi|virtio-mem,\ hotplug_size=,\ hotplugged_size=,\ - prefault=on|off,thp=on|off\"", + prefault=on|off,reserve=on|off,thp=on|off\"", ) .default_value(default_memory) .group("vm-config"), @@ -335,7 +335,7 @@ fn get_cli_options_sorted( host_numa_node=,\ id=,hotplug_size=,\ hotplugged_size=,\ - prefault=on|off\"", + prefault=on|off,reserve=on|off\"", ) .num_args(1..) .action(ArgAction::Append) @@ -1003,6 +1003,7 @@ mod unit_tests { hugepages: false, hugepage_size: None, prefault: false, + reserve: false, zones: None, thp: true, }, diff --git a/docs/memory.md b/docs/memory.md index fb42e8937..f440c3574 100644 --- a/docs/memory.md +++ b/docs/memory.md @@ -20,13 +20,14 @@ struct MemoryConfig { hugepages: bool, hugepage_size: Option, prefault: bool, + reserve: bool, thp: bool, zones: Option>, } ``` ``` ---memory Memory parameters "size=,mergeable=on|off,shared=on|off,hugepages=on|off,hugepage_size=,hotplug_method=acpi|virtio-mem,hotplug_size=,hotplugged_size=,prefault=on|off,thp=on|off" [default: size=512M,thp=on] +--memory Memory parameters "size=,mergeable=on|off,shared=on|off,hugepages=on|off,hugepage_size=,hotplug_method=acpi|virtio-mem,hotplug_size=,hotplugged_size=,prefault=on|off,reserve=on|off,thp=on|off" [default: size=512M,thp=on] ``` ### `size` @@ -177,6 +178,32 @@ _Example_ --memory size=1G,prefault=on ``` +### `reserve` + +Specifies if guest memory should be `mmap(2)`-ed _without_ the `MAP_NORESERVE` +flag, asking the kernel to reserve the backing pages (swap space, or huge pages +for hugepage-backed memory) for the whole region up front at `mmap` time. + +By default Cloud Hypervisor maps guest memory with `MAP_NORESERVE`, so VM +creation succeeds even when the backing pool cannot satisfy the full guest size. +The shortfall then surfaces only later, as a `SIGBUS` delivered to the guest when +it faults a page the pool cannot back. With `reserve=on` the reservation is made +when the memory is mapped, so an over-committed configuration fails cleanly at VM +creation with an out-of-memory error instead of crashing the guest at run time. +This is most useful for hugepage-backed memory, where the huge pages are reserved +from the pool. + +Unlike `prefault`, this does not populate or fault in the memory, so it does not +slow down boot; it only reserves it. + +By default this option is turned off. + +_Example_ + +``` +--memory size=1G,hugepages=on,reserve=on +``` + ### `thp` Specifies if private anonymous memory for the guest (i.e. `shared=off` and no @@ -214,12 +241,13 @@ struct MemoryZoneConfig { hotplug_size: Option, hotplugged_size: Option, prefault: bool, + reserve: bool, mergeable: bool, } ``` ``` ---memory-zone User defined memory zone parameters "size=,file=,shared=on|off,hugepages=on|off,hugepage_size=,host_numa_node=,id=,hotplug_size=,hotplugged_size=,prefault=on|off,mergeable=on|off" +--memory-zone User defined memory zone parameters "size=,file=,shared=on|off,hugepages=on|off,hugepage_size=,host_numa_node=,id=,hotplug_size=,hotplugged_size=,prefault=on|off,reserve=on|off,mergeable=on|off" ``` This parameter expects one or more occurrences, allowing for a list of memory @@ -423,6 +451,34 @@ _Example_ --memory-zone id=mem0,size=1G,prefault=on ``` +### `reserve` + +Specifies if the memory for this zone should be `mmap(2)`-ed _without_ the +`MAP_NORESERVE` flag, asking the kernel to reserve the backing pages (swap space, +or huge pages for hugepage-backed memory) for the whole zone up front at `mmap` +time. + +By default Cloud Hypervisor maps guest memory with `MAP_NORESERVE`, so VM +creation succeeds even when the backing pool cannot satisfy the full zone size. +The shortfall then surfaces only later, as a `SIGBUS` delivered to the guest when +it faults a page the pool cannot back. With `reserve=on` the reservation is made +when the memory is mapped, so an over-committed configuration fails cleanly at VM +creation with an out-of-memory error instead of crashing the guest at run time. +This is most useful for hugepage-backed zones, where the huge pages are reserved +from the pool. + +Unlike `prefault`, this does not populate or fault in the memory, so it does not +slow down boot; it only reserves it. + +By default this option is turned off. + +_Example_ + +``` +--memory size=0 +--memory-zone id=mem0,size=1G,hugepages=on,reserve=on +``` + ### `mergeable` Specifies if the pages from this memory zone must be marked as _mergeable_, diff --git a/fuzz/fuzz_targets/http_api.rs b/fuzz/fuzz_targets/http_api.rs index 16957983e..1f0551acc 100644 --- a/fuzz/fuzz_targets/http_api.rs +++ b/fuzz/fuzz_targets/http_api.rs @@ -150,6 +150,7 @@ impl RequestHandler for StubApiRequestHandler { hugepages: false, hugepage_size: None, prefault: false, + reserve: false, zones: None, thp: true, }, diff --git a/fuzz/fuzz_targets/mem.rs b/fuzz/fuzz_targets/mem.rs index e430e195a..475f24f7d 100644 --- a/fuzz/fuzz_targets/mem.rs +++ b/fuzz/fuzz_targets/mem.rs @@ -148,6 +148,7 @@ fn create_dummy_virtio_mem(bytes: &[u8; VIRTIO_MEM_DATA_SIZE]) -> (Mem, Arc("reserve") + .map_err(Error::ParseMemory)? + .unwrap_or(Toggle(false)) + .0; let thp = parser .convert::("thp") .map_err(Error::ParseMemory)? @@ -1125,6 +1131,7 @@ impl MemoryConfig { .add("hotplug_size") .add("hotplugged_size") .add("prefault") + .add("reserve") .add("mergeable"); parser.parse(memory_zone).map_err(Error::ParseMemoryZone)?; @@ -1166,6 +1173,11 @@ impl MemoryConfig { .map_err(Error::ParseMemoryZone)? .unwrap_or(Toggle(false)) .0; + let reserve = parser + .convert::("reserve") + .map_err(Error::ParseMemoryZone)? + .unwrap_or(Toggle(false)) + .0; let mergeable = parser .convert::("mergeable") .map_err(Error::ParseMemoryZone)? @@ -1183,6 +1195,7 @@ impl MemoryConfig { hotplug_size, hotplugged_size, prefault, + reserve, mergeable, }); } @@ -1201,6 +1214,7 @@ impl MemoryConfig { hugepages, hugepage_size, prefault, + reserve, zones, thp, }) @@ -3974,6 +3988,20 @@ mod unit_tests { ..Default::default() } ); + // reserve=on on a zone + assert_eq!( + MemoryConfig::parse("size=0", Some(vec!["id=mem0,size=1G,reserve=on"]))?, + MemoryConfig { + size: 0, + zones: Some(vec![MemoryZoneConfig { + id: "mem0".to_string(), + size: 1 << 30, + reserve: true, + ..Default::default() + }]), + ..Default::default() + } + ); Ok(()) } @@ -4038,6 +4066,16 @@ mod unit_tests { ..Default::default() } ); + // reserve=on opts out of MAP_NORESERVE + assert_eq!( + MemoryConfig::parse("size=1G,hugepages=on,reserve=on", None)?, + MemoryConfig { + size: 1 << 30, + hugepages: true, + reserve: true, + ..Default::default() + } + ); Ok(()) } @@ -5227,6 +5265,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" hugepages: false, hugepage_size: None, prefault: false, + reserve: false, zones: None, thp: true, }, diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 25c769854..ba279f7b6 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -5387,6 +5387,7 @@ impl IvshmemOps for IvshmemHandler { 0, size, false, + false, true, false, None, diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 378dafc4e..2d80592c1 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -2731,6 +2731,7 @@ mod unit_tests { hugepages: false, hugepage_size: None, prefault: false, + reserve: false, zones: None, thp: true, }, diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index bebcc8fa4..8919e601c 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -278,6 +278,7 @@ pub struct MemoryManager { hugepages: bool, hugepage_size: Option, prefault: bool, + reserve: bool, thp: bool, user_provided_zones: bool, snapshot_memory_ranges: MemoryRangeTable, @@ -727,6 +728,7 @@ impl MemoryManager { region_start, region_size as usize, prefault.unwrap_or(zone.prefault), + zone.reserve, zone.shared, zone.hugepages, zone.hugepage_size, @@ -829,6 +831,7 @@ impl MemoryManager { GuestAddress(guest_ram_mapping.gpa), guest_ram_mapping.size as usize, prefault.unwrap_or(zone_config.prefault), + zone_config.reserve, zone_config.shared, zone_config.hugepages, zone_config.hugepage_size, @@ -1528,6 +1531,7 @@ impl MemoryManager { hotplug_size: config.hotplug_size, hotplugged_size: config.hotplugged_size, prefault: config.prefault, + reserve: config.reserve, mergeable: config.mergeable, }]; @@ -1762,6 +1766,7 @@ impl MemoryManager { start_addr, hotplug_size as usize, prefault.unwrap_or(zone.prefault), + zone.reserve, zone.shared, zone.hugepages, zone.hugepage_size, @@ -1875,6 +1880,7 @@ impl MemoryManager { hugepages: config.hugepages, hugepage_size: config.hugepage_size, prefault: config.prefault, + reserve: config.reserve, user_provided_zones, snapshot_memory_ranges: MemoryRangeTable::default(), memory_zones, @@ -2042,6 +2048,7 @@ impl MemoryManager { file_offset: u64, size: usize, prefault: bool, + reserve: bool, shared: bool, hugepages: bool, hugepage_size: Option, @@ -2049,7 +2056,7 @@ impl MemoryManager { existing_memory_file: Option, thp: bool, ) -> Result, Error> { - let mut mmap_flags = libc::MAP_NORESERVE; + let mut mmap_flags = if reserve { 0 } else { libc::MAP_NORESERVE }; // The duplication of mmap_flags ORing here is unfortunate but it also makes // the complexity of the handling clear. @@ -2179,6 +2186,7 @@ impl MemoryManager { start_addr: GuestAddress, size: usize, prefault: bool, + reserve: bool, shared: bool, hugepages: bool, hugepage_size: Option, @@ -2191,6 +2199,7 @@ impl MemoryManager { file_offset, size, prefault, + reserve, shared, hugepages, hugepage_size, @@ -2306,6 +2315,7 @@ impl MemoryManager { start_addr, size, self.prefault, + self.reserve, self.shared, self.hugepages, self.hugepage_size, diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 3f5c1544e..a08f61c20 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -245,6 +245,8 @@ pub struct MemoryZoneConfig { #[serde(default)] pub prefault: bool, #[serde(default)] + pub reserve: bool, + #[serde(default)] pub mergeable: bool, } @@ -293,6 +295,8 @@ pub struct MemoryConfig { #[serde(default)] pub prefault: bool, #[serde(default)] + pub reserve: bool, + #[serde(default)] pub zones: Option>, #[serde(default = "default_memoryconfig_thp")] pub thp: bool, @@ -312,6 +316,7 @@ impl Default for MemoryConfig { hugepages: false, hugepage_size: None, prefault: false, + reserve: false, zones: None, thp: true, }