Skip to content

Commit

Permalink
qmemman: use memory hotplug in qmemman
Browse files Browse the repository at this point in the history
If VM supports memory hotplug, start it with maxmem=memory and then use
memory hotplug to (potentially) increase its memory. Qmemman almost had
all the needed parts - especially mem_set function already adjusts
maxmem too. The missing part was letting it know actual maxmem - do that
via xenstore (as qmemman uses xenstore heavily already).

The use of memory hotplug can be enabled (or disabled) explicitly via
'memory-hotplug' feature, or detected based on
'memory-hotplug-supported' file in dom0-provided kernel dir, or
'supported-feature.memory-hotplug' for VM-provided kernel.

Fixes QubesOS/qubes-issues#7956
  • Loading branch information
marmarek committed Dec 27, 2022
1 parent 8ce26eb commit c769961
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 5 deletions.
19 changes: 15 additions & 4 deletions qubes/qmemman/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def __init__(self, id):
self.mem_used = None # used memory, computed based on meminfo
self.id = id # domain id
self.last_target = 0 # the last memset target
self.use_hoplug = False # use memory hotplug for mem-set
self.no_progress = False # no react to memset
self.slow_memset_react = False # slow react to memset (after few
# tries still above target)
Expand Down Expand Up @@ -96,7 +97,7 @@ def get_free_xen_memory(self):
# used - do not count it as "free", because domain is free to use it
# at any time
# assumption: self.refresh_memactual was called before
# (so domdict[id].memory_actual is up to date)
# (so domdict[id].memory_actual is up-to-date)
assigned_but_unused = functools.reduce(
lambda acc, dom: acc + max(0, dom.last_target-dom.memory_current),
self.domdict.values(),
Expand Down Expand Up @@ -127,9 +128,16 @@ def refresh_memactual(self):
self.domdict[id].memory_current,
self.domdict[id].last_target
)
self.domdict[id].memory_maximum = self.xs.read('', '/local/domain/%s/memory/static-max' % str(id))
if self.domdict[id].memory_maximum:
self.domdict[id].memory_maximum = int(self.domdict[id].memory_maximum)*1024
hotplug_max = self.xs.read(
'', '/local/domain/%s/memory/hotplug-max' % str(id))
static_max = self.xs.read(
'', '/local/domain/%s/memory/static-max' % str(id))
if hotplug_max:
self.domdict[id].memory_maximum = int(hotplug_max)*1024
self.domdict[id].use_hotplug = True
elif static_max:
self.domdict[id].memory_maximum = int(static_max)*1024
self.domdict[id].use_hotplug = False
else:
self.domdict[id].memory_maximum = self.ALL_PHYS_MEM
# the previous line used to be
Expand Down Expand Up @@ -174,6 +182,9 @@ def mem_set(self, id, val):
# handle Xen view of memory
self.xs.write('', '/local/domain/' + id + '/memory/target',
str(int(val/1024 - 16 * 1024)))
if self.domdict[id].use_hotplug:
self.xs.write('', '/local/domain/' + id + '/memory/static-max',
str(int(val / 1024)))

# this is called at the end of ballooning, when we have Xen free mem already
# make sure that past mem_set will not decrease Xen free mem
Expand Down
27 changes: 27 additions & 0 deletions qubes/vm/qubesvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import asyncio
import base64
import grp
import pathlib
import re
import os
import os.path
Expand Down Expand Up @@ -1627,6 +1628,28 @@ def is_memory_balancing_possible(self):
return False
return True

@property
def use_memory_hotplug(self):
"""Use memory hotplug for memory balancing.
This is preferred if supported, because it has less initial overhead
and reduces Xen's attack surface.
The support needs to be enabled in the VM's kernel.
"""
feature = self.features.check_with_template('memory-hotplug', None)
if feature is not None:
return bool(feature)
# if not explicitly set, check if support is advertised
# for dom0-provided kernel - check there
if self.kernel:
return (pathlib.Path(self.storage.kernels_dir) /
'memory-hotplug-supported').exists()
# otherwise - check advertised VM's features
feature = self.features.check_with_template(
'supported-feature.memory-hotplug', None)
if feature is not None:
return bool(feature)
return False

def request_memory(self, mem_required=None):
if not qmemman_present:
return None
Expand Down Expand Up @@ -2277,6 +2300,10 @@ def create_qdb_entries(self):
self.app.vmm.xs.set_permissions('',
f"{xs_basedir}/memory/meminfo",
[{'dom': self.xid}])
if self.use_memory_hotplug:
self.app.vmm.xs.write('',
'/local/domain/{}/memory/hotplug-max',
str(self.maxmem))

self.fire_event('domain-qdb-create')

Expand Down
2 changes: 1 addition & 1 deletion templates/libvirt/xen.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<name>{{ vm.name }}</name>
<uuid>{{ vm.uuid }}</uuid>
{% if ((vm.virt_mode == 'hvm' and vm.devices['pci'].persistent() | list)
or vm.maxmem == 0) -%}
or vm.maxmem == 0 or vm.use_memory_hotplug) -%}
<memory unit="MiB">{{ vm.memory }}</memory>
{% else -%}
<memory unit="MiB">{{ vm.maxmem }}</memory>
Expand Down

0 comments on commit c769961

Please sign in to comment.