mirror of
https://github.com/xen-project/xen.git
synced 2026-10-29 02:30:03 +01:00
Tmem, when called from a tmem-capable (paravirtualized) guest, makes
use of otherwise unutilized ("fallow") memory to create and manage
pools of pages that can be accessed from the guest either as
"ephemeral" pages or as "persistent" pages. In either case, the pages
are not directly addressible by the guest, only copied to and fro via
the tmem interface. Ephemeral pages are a nice place for a guest to
put recently evicted clean pages that it might need again; these pages
can be reclaimed synchronously by Xen for other guests or other uses.
Persistent pages are a nice place for a guest to put "swap" pages to
avoid sending them to disk. These pages retain data as long as the
guest lives, but count against the guest memory allocation.
Tmem pages may optionally be compressed and, in certain cases, can be
shared between guests. Tmem also handles concurrency nicely and
provides limited QoS settings to combat malicious DoS attempts.
Save/restore and live migration support is not yet provided.
Tmem is primarily targeted for an x86 64-bit hypervisor. On a 32-bit
x86 hypervisor, it has limited functionality and testing due to
limitations of the xen heap. Nearly all of tmem is
architecture-independent; three routines remain to be ported to ia64
and it should work on that architecture too. It is also structured to
be portable to non-Xen environments.
Tmem defaults off (for now) and must be enabled with a "tmem" xen boot
option (and does nothing unless a tmem-capable guest is running). The
"tmem_compress" boot option enables compression which takes about 10x
more CPU but approximately doubles the number of pages that can be
stored.
Tmem can be controlled via several "xm" commands and many interesting
tmem statistics can be obtained. A README and internal specification
will follow, but lots of useful prose about tmem, as well as Linux
patches, can be found at http://oss.oracle.com/projects/tmem .
Signed-off-by: Dan Magenheimer <dan.magenheimer@oracle.com>
237 lines
9.7 KiB
Python
237 lines
9.7 KiB
Python
#===========================================================================
|
|
# This library is free software; you can redistribute it and/or
|
|
# modify it under the terms of version 2.1 of the GNU Lesser General Public
|
|
# License as published by the Free Software Foundation.
|
|
#
|
|
# This library is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
# Lesser General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Lesser General Public
|
|
# License along with this library; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#============================================================================
|
|
# Copyright (C) 2004, 2005 Mike Wray <mike.wray@hp.com>
|
|
# Copyright (C) 2005 XenSource Ltd
|
|
#============================================================================
|
|
|
|
|
|
import time
|
|
|
|
import xen.lowlevel.xc
|
|
|
|
import XendDomain
|
|
import XendOptions
|
|
from XendLogging import log
|
|
from XendError import VmError
|
|
import osdep
|
|
from xen.xend.XendConstants import *
|
|
|
|
RETRY_LIMIT = 20
|
|
RETRY_LIMIT_INCR = 5
|
|
##
|
|
# The time to sleep between retries grows linearly, using this value (in
|
|
# seconds). When the system is lightly loaded, memory should be scrubbed and
|
|
# returned to the system very quickly, whereas when it is loaded, the system
|
|
# needs idle time to get the scrubbing done. This linear growth accommodates
|
|
# such requirements.
|
|
SLEEP_TIME_GROWTH = 0.1
|
|
|
|
# A mapping between easy-to-remember labels and the more verbose
|
|
# label actually shown in the PROC_XEN_BALLOON file.
|
|
#labels = { 'current' : 'Current allocation',
|
|
# 'target' : 'Requested target',
|
|
# 'low-balloon' : 'Low-mem balloon',
|
|
# 'high-balloon' : 'High-mem balloon',
|
|
# 'limit' : 'Xen hard limit' }
|
|
|
|
def _get_proc_balloon(label):
|
|
"""Returns the value for the named label. Returns None if the label was
|
|
not found or the value was non-numeric."""
|
|
|
|
return osdep.lookup_balloon_stat(label)
|
|
|
|
def get_dom0_current_alloc():
|
|
"""Returns the current memory allocation (in KiB) of dom0."""
|
|
|
|
kb = _get_proc_balloon('current')
|
|
if kb == None:
|
|
raise VmError('Failed to query current memory allocation of dom0.')
|
|
return kb
|
|
|
|
def get_dom0_target_alloc():
|
|
"""Returns the target memory allocation (in KiB) of dom0."""
|
|
|
|
kb = _get_proc_balloon('target')
|
|
if kb == None:
|
|
raise VmError('Failed to query target memory allocation of dom0.')
|
|
return kb
|
|
|
|
def free(need_mem, dominfo):
|
|
"""Balloon out memory from the privileged domain so that there is the
|
|
specified required amount (in KiB) free.
|
|
"""
|
|
|
|
# We check whether there is enough free memory, and if not, instruct dom0
|
|
# to balloon out to free some up. Memory freed by a destroyed domain may
|
|
# not appear in the free_memory field immediately, because it needs to be
|
|
# scrubbed before it can be released to the free list, which is done
|
|
# asynchronously by Xen; ballooning is asynchronous also. Such memory
|
|
# does, however, need to be accounted for when calculating how much dom0
|
|
# needs to balloon. No matter where we expect the free memory to come
|
|
# from, we need to wait for it to become available.
|
|
#
|
|
# We are not allowed to balloon below dom0_min_mem, or if dom0_ballooning
|
|
# is False, we cannot balloon at all. Memory can still become available
|
|
# through a rebooting domain, however.
|
|
#
|
|
# Eventually, we time out (presumably because there really isn't enough
|
|
# free memory).
|
|
#
|
|
# We don't want to set the memory target (triggering a watch) when that
|
|
# has already been done, but we do want to respond to changing memory
|
|
# usage, so we recheck the required alloc each time around the loop, but
|
|
# track the last used value so that we don't trigger too many watches.
|
|
|
|
xoptions = XendOptions.instance()
|
|
dom0 = XendDomain.instance().privilegedDomain()
|
|
xc = xen.lowlevel.xc.xc()
|
|
|
|
try:
|
|
dom0_min_mem = xoptions.get_dom0_min_mem() * 1024
|
|
dom0_ballooning = xoptions.get_enable_dom0_ballooning()
|
|
dom0_alloc = get_dom0_current_alloc()
|
|
|
|
retries = 0
|
|
sleep_time = SLEEP_TIME_GROWTH
|
|
new_alloc = 0
|
|
last_new_alloc = None
|
|
last_free = None
|
|
rlimit = RETRY_LIMIT
|
|
|
|
# stop tmem from absorbing any more memory (must THAW when done!)
|
|
xc.tmem_control(0,TMEMC_FREEZE,-1, 0, 0, "")
|
|
|
|
# If unreasonable memory size is required, we give up waiting
|
|
# for ballooning or scrubbing, as if had retried.
|
|
physinfo = xc.physinfo()
|
|
free_mem = physinfo['free_memory']
|
|
scrub_mem = physinfo['scrub_memory']
|
|
total_mem = physinfo['total_memory']
|
|
if dom0_ballooning:
|
|
max_free_mem = total_mem - dom0_min_mem
|
|
else:
|
|
max_free_mem = total_mem - dom0_alloc
|
|
if need_mem >= max_free_mem:
|
|
retries = rlimit
|
|
|
|
freeable_mem = free_mem + scrub_mem
|
|
if freeable_mem < need_mem and need_mem < max_free_mem:
|
|
# flush memory from tmem to scrub_mem and reobtain physinfo
|
|
need_tmem_kb = need_mem - freeable_mem
|
|
tmem_kb = xc.tmem_control(0,TMEMC_FLUSH,-1, need_tmem_kb, 0, "")
|
|
log.debug("Balloon: tmem relinquished %d KiB of %d KiB requested.",
|
|
tmem_kb, need_tmem_kb)
|
|
physinfo = xc.physinfo()
|
|
free_mem = physinfo['free_memory']
|
|
scrub_mem = physinfo['scrub_memory']
|
|
|
|
# Check whethercurrent machine is a numa system and the new
|
|
# created hvm has all its vcpus in the same node, if all the
|
|
# conditions above are fit. We will wait until all the pages
|
|
# in scrub list are freed (if waiting time go beyond 20s,
|
|
# we will stop waiting it.)
|
|
if physinfo['nr_nodes'] > 1 and retries == 0:
|
|
oldnode = -1
|
|
waitscrub = 1
|
|
vcpus = dominfo.info['cpus'][0]
|
|
for vcpu in vcpus:
|
|
nodenum = 0
|
|
for node in physinfo['node_to_cpu']:
|
|
for cpu in node:
|
|
if vcpu == cpu:
|
|
if oldnode == -1:
|
|
oldnode = nodenum
|
|
elif oldnode != nodenum:
|
|
waitscrub = 0
|
|
nodenum = nodenum + 1
|
|
|
|
if waitscrub == 1 and scrub_mem > 0:
|
|
log.debug("wait for scrub %s", scrub_mem)
|
|
while scrub_mem > 0 and retries < rlimit:
|
|
time.sleep(sleep_time)
|
|
physinfo = xc.physinfo()
|
|
free_mem = physinfo['free_memory']
|
|
scrub_mem = physinfo['scrub_memory']
|
|
retries += 1
|
|
sleep_time += SLEEP_TIME_GROWTH
|
|
log.debug("scrub for %d times", retries)
|
|
|
|
retries = 0
|
|
sleep_time = SLEEP_TIME_GROWTH
|
|
|
|
while retries < rlimit:
|
|
physinfo = xc.physinfo()
|
|
free_mem = physinfo['free_memory']
|
|
scrub_mem = physinfo['scrub_memory']
|
|
|
|
if free_mem >= need_mem:
|
|
log.debug("Balloon: %d KiB free; need %d; done.",
|
|
free_mem, need_mem)
|
|
return
|
|
|
|
if retries == 0:
|
|
rlimit += ((need_mem - free_mem)/1024/1024) * RETRY_LIMIT_INCR
|
|
log.debug("Balloon: %d KiB free; %d to scrub; need %d; retries: %d.",
|
|
free_mem, scrub_mem, need_mem, rlimit)
|
|
|
|
if dom0_ballooning:
|
|
dom0_alloc = get_dom0_current_alloc()
|
|
new_alloc = dom0_alloc - (need_mem - free_mem - scrub_mem)
|
|
|
|
if free_mem + scrub_mem >= need_mem:
|
|
if last_new_alloc == None:
|
|
log.debug("Balloon: waiting on scrubbing")
|
|
last_new_alloc = dom0_alloc
|
|
else:
|
|
if (new_alloc >= dom0_min_mem and
|
|
new_alloc != last_new_alloc):
|
|
new_alloc_mb = new_alloc / 1024 # Round down
|
|
log.debug("Balloon: setting dom0 target to %d MiB.",
|
|
new_alloc_mb)
|
|
dom0.setMemoryTarget(new_alloc_mb)
|
|
last_new_alloc = new_alloc
|
|
# Continue to retry, waiting for ballooning or scrubbing.
|
|
|
|
time.sleep(sleep_time)
|
|
if retries < 2 * RETRY_LIMIT:
|
|
sleep_time += SLEEP_TIME_GROWTH
|
|
if last_free != None and last_free >= free_mem + scrub_mem:
|
|
retries += 1
|
|
last_free = free_mem + scrub_mem
|
|
|
|
# Not enough memory; diagnose the problem.
|
|
if not dom0_ballooning:
|
|
raise VmError(('Not enough free memory and enable-dom0-ballooning '
|
|
'is False, so I cannot release any more. '
|
|
'I need %d KiB but only have %d.') %
|
|
(need_mem, free_mem))
|
|
elif new_alloc < dom0_min_mem:
|
|
raise VmError(
|
|
('I need %d KiB, but dom0_min_mem is %d and shrinking to '
|
|
'%d KiB would leave only %d KiB free.') %
|
|
(need_mem, dom0_min_mem, dom0_min_mem,
|
|
free_mem + scrub_mem + dom0_alloc - dom0_min_mem))
|
|
else:
|
|
dom0_start_alloc_mb = get_dom0_current_alloc() / 1024
|
|
dom0.setMemoryTarget(dom0_start_alloc_mb)
|
|
raise VmError(
|
|
('Not enough memory is available, and dom0 cannot'
|
|
' be shrunk any further'))
|
|
|
|
finally:
|
|
# allow tmem to accept pages again
|
|
xc.tmem_control(0,TMEMC_THAW,-1, 0, 0, "")
|
|
del xc
|