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:
JP Kobryn
2026-04-08 18:52:01 -07:00
committed by Rob Bradford
parent 3a23e2f841
commit bdc7a6947d
4 changed files with 145 additions and 11 deletions

View File

@@ -138,16 +138,18 @@ pub struct MemoryZone {
shared: bool,
hugepages: bool,
backing_page_size: u64,
mergeable: bool,
}
impl MemoryZone {
fn new(shared: bool, hugepages: bool, backing_page_size: u64) -> Self {
fn new(shared: bool, hugepages: bool, backing_page_size: u64, mergeable: bool) -> Self {
Self {
regions: Vec::new(),
virtio_mem_zone: None,
shared,
hugepages,
backing_page_size,
mergeable,
}
}
@@ -607,7 +609,7 @@ impl MemoryManager {
// Add zone id to the list of memory zones.
memory_zones.insert(
zone.id.clone(),
MemoryZone::new(zone.shared, zone.hugepages, zone_align_size),
MemoryZone::new(zone.shared, zone.hugepages, zone_align_size, zone.mergeable),
);
for ram_region in ram_regions.iter() {
@@ -701,7 +703,12 @@ impl MemoryManager {
}
memory_zones.insert(
zone.id.clone(),
MemoryZone::new(zone.shared, zone.hugepages, zone_align_size),
MemoryZone::new(
zone.shared,
zone.hugepages,
zone_align_size,
zone.mergeable,
),
);
}
@@ -733,7 +740,12 @@ impl MemoryManager {
let zone_page_size = memory_zone_get_align_size(zone_config)?;
memory_zones.insert(
zone_config.id.clone(),
MemoryZone::new(zone_config.shared, zone_config.hugepages, zone_page_size),
MemoryZone::new(
zone_config.shared,
zone_config.hugepages,
zone_page_size,
zone_config.mergeable,
),
);
}
@@ -1295,6 +1307,7 @@ impl MemoryManager {
hotplug_size: config.hotplug_size,
hotplugged_size: config.hotplugged_size,
prefault: config.prefault,
mergeable: config.mergeable,
}];
Ok((config.size, zones, allow_mem_hotplug))
@@ -1316,10 +1329,10 @@ impl MemoryManager {
regions.push((virtio_mem_zone.region().clone(), true));
}
list.push((zone_id.clone(), regions));
list.push((zone_id.clone(), regions, memory_zone.mergeable));
}
for (zone_id, regions) in list {
for (zone_id, regions, zone_mergeable) in list {
for (region, virtio_mem) in regions {
// SAFETY: guaranteed by GuestRegionMmap invariants
let slot = unsafe {
@@ -1327,7 +1340,7 @@ impl MemoryManager {
region.start_addr().raw_value(),
region.len().try_into().unwrap(),
region.as_ptr(),
self.mergeable,
zone_mergeable,
false,
self.log_dirty,
)
@@ -2087,7 +2100,9 @@ impl MemoryManager {
region.start_addr().0,
region.len().try_into().unwrap(),
region.as_ptr(),
self.mergeable,
self.memory_zones
.get(DEFAULT_MEMORY_ZONE)
.map_or(self.mergeable, |z| z.mergeable),
false,
self.log_dirty,
)