mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
vmm: add per-zone mergeable option to --memory-zone
Add a `mergeable` field to `MemoryZoneConfig` so that KSM page merging can be enabled selectively per memory zone rather than globally for all guest RAM. Previously, `MADV_MERGEABLE` was only controllable via the top-level `--memory mergeable=on` flag, which applied uniformly to all regions. With this change, users can leave boot memory unmerged while enabling KSM only on hotplug zones: --memory size=0,hotplug_method=virtio-mem --memory-zone id=boot,size=512M,shared=on,mergeable=off --memory-zone id=hotplug,size=256M,hotplug_size=1G,shared=off,mergeable=on The `MemoryZone` runtime struct now carries the `mergeable` flag so that both `allocate_address_space` and `add_ram_region` can apply per-zone `MADV_MERGEABLE` instead of the global `self.mergeable`. The top-level `--memory mergeable=on` path continues to work unchanged: the default zone is synthesised from `MemoryConfig` and inherits its `mergeable` value. AI/LLM disclosure: this patch was co-authored with GitHub Copilot and Claude Code (Opus 4.6). Signed-off-by: JP Kobryn <inwardvessel@gmail.com>
This commit is contained in:
+89
-1
@@ -1006,7 +1006,8 @@ impl MemoryConfig {
|
||||
.add("host_numa_node")
|
||||
.add("hotplug_size")
|
||||
.add("hotplugged_size")
|
||||
.add("prefault");
|
||||
.add("prefault")
|
||||
.add("mergeable");
|
||||
parser.parse(memory_zone).map_err(Error::ParseMemoryZone)?;
|
||||
|
||||
let id = parser.get("id").ok_or(Error::ParseMemoryZoneIdMissing)?;
|
||||
@@ -1047,6 +1048,11 @@ impl MemoryConfig {
|
||||
.map_err(Error::ParseMemoryZone)?
|
||||
.unwrap_or(Toggle(false))
|
||||
.0;
|
||||
let mergeable = parser
|
||||
.convert::<Toggle>("mergeable")
|
||||
.map_err(Error::ParseMemoryZone)?
|
||||
.unwrap_or(Toggle(mergeable))
|
||||
.0;
|
||||
|
||||
zones.push(MemoryZoneConfig {
|
||||
id,
|
||||
@@ -1059,6 +1065,7 @@ impl MemoryConfig {
|
||||
hotplug_size,
|
||||
hotplugged_size,
|
||||
prefault,
|
||||
mergeable,
|
||||
});
|
||||
}
|
||||
Some(zones)
|
||||
@@ -3750,6 +3757,87 @@ mod unit_tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mem_zone_parsing() -> Result<()> {
|
||||
// mergeable defaults to false
|
||||
assert_eq!(
|
||||
MemoryConfig::parse("size=0", Some(vec!["id=mem0,size=1G"]))?,
|
||||
MemoryConfig {
|
||||
size: 0,
|
||||
zones: Some(vec![MemoryZoneConfig {
|
||||
id: "mem0".to_string(),
|
||||
size: 1 << 30,
|
||||
..Default::default()
|
||||
}]),
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
// mergeable=on
|
||||
assert_eq!(
|
||||
MemoryConfig::parse("size=0", Some(vec!["id=mem0,size=1G,mergeable=on"]))?,
|
||||
MemoryConfig {
|
||||
size: 0,
|
||||
zones: Some(vec![MemoryZoneConfig {
|
||||
id: "mem0".to_string(),
|
||||
size: 1 << 30,
|
||||
mergeable: true,
|
||||
..Default::default()
|
||||
}]),
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
// mergeable=off is explicit false
|
||||
assert_eq!(
|
||||
MemoryConfig::parse("size=0", Some(vec!["id=mem0,size=1G,mergeable=off"]))?,
|
||||
MemoryConfig {
|
||||
size: 0,
|
||||
zones: Some(vec![MemoryZoneConfig {
|
||||
id: "mem0".to_string(),
|
||||
size: 1 << 30,
|
||||
mergeable: false,
|
||||
..Default::default()
|
||||
}]),
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
// per-zone mergeable independent of global mergeable
|
||||
assert_eq!(
|
||||
MemoryConfig::parse(
|
||||
"size=1G,mergeable=off",
|
||||
Some(vec!["id=hotplug,size=0,hotplug_size=4G,mergeable=on"])
|
||||
)?,
|
||||
MemoryConfig {
|
||||
size: 1 << 30,
|
||||
mergeable: false,
|
||||
hotplug_method: HotplugMethod::Acpi,
|
||||
zones: Some(vec![MemoryZoneConfig {
|
||||
id: "hotplug".to_string(),
|
||||
size: 0,
|
||||
hotplug_size: Some(4 << 30),
|
||||
mergeable: true,
|
||||
..Default::default()
|
||||
}]),
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
// global mergeable=on inherited by zone with no explicit mergeable
|
||||
assert_eq!(
|
||||
MemoryConfig::parse("size=0,mergeable=on", Some(vec!["id=mem0,size=1G"]))?,
|
||||
MemoryConfig {
|
||||
size: 0,
|
||||
mergeable: true,
|
||||
zones: Some(vec![MemoryZoneConfig {
|
||||
id: "mem0".to_string(),
|
||||
size: 1 << 30,
|
||||
mergeable: true,
|
||||
..Default::default()
|
||||
}]),
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mem_parsing() -> Result<()> {
|
||||
assert_eq!(MemoryConfig::parse("", None)?, MemoryConfig::default());
|
||||
|
||||
Reference in New Issue
Block a user