Files
xen/tools/libxc/xc_hvm_save.c
T
2007-04-09 11:11:23 +01:00

756 lines
22 KiB
C

/******************************************************************************
* xc_hvm_save.c
*
* Save the state of a running HVM guest.
*
* Copyright (c) 2003, K A Fraser.
* Copyright (c) 2006 Intel Corperation
* rewriten for hvm guest by Zhai Edwin <edwin.zhai@intel.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 59 Temple
* Place - Suite 330, Boston, MA 02111-1307 USA.
*
*/
#include <inttypes.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include "xc_private.h"
#include "xg_private.h"
#include "xg_save_restore.h"
#include <xen/hvm/e820.h>
#include <xen/hvm/params.h>
/*
** Default values for important tuning parameters. Can override by passing
** non-zero replacement values to xc_hvm_save().
**
** XXX SMH: should consider if want to be able to override MAX_MBIT_RATE too.
**
*/
#define DEF_MAX_ITERS 29 /* limit us to 30 times round loop */
#define DEF_MAX_FACTOR 3 /* never send more than 3x nr_pfns */
/* Shared-memory bitmaps for getting log-dirty bits from qemu */
static unsigned long *qemu_bitmaps[2];
static int qemu_active;
static int qemu_non_active;
/*
** During (live) save/migrate, we maintain a number of bitmaps to track
** which pages we have to send, to fixup, and to skip.
*/
#define BITS_PER_LONG (sizeof(unsigned long) * 8)
#define BITS_TO_LONGS(bits) (((bits)+BITS_PER_LONG-1)/BITS_PER_LONG)
#define BITMAP_SIZE (BITS_TO_LONGS(pfn_array_size) * sizeof(unsigned long))
#define BITMAP_ENTRY(_nr,_bmap) \
((unsigned long *)(_bmap))[(_nr)/BITS_PER_LONG]
#define BITMAP_SHIFT(_nr) ((_nr) % BITS_PER_LONG)
static inline int test_bit (int nr, volatile void * addr)
{
return (BITMAP_ENTRY(nr, addr) >> BITMAP_SHIFT(nr)) & 1;
}
static inline void clear_bit (int nr, volatile void * addr)
{
BITMAP_ENTRY(nr, addr) &= ~(1UL << BITMAP_SHIFT(nr));
}
static inline int permute( int i, int nr, int order_nr )
{
/* Need a simple permutation function so that we scan pages in a
pseudo random order, enabling us to get a better estimate of
the domain's page dirtying rate as we go (there are often
contiguous ranges of pfns that have similar behaviour, and we
want to mix them up. */
/* e.g. nr->oder 15->4 16->4 17->5 */
/* 512MB domain, 128k pages, order 17 */
/*
QPONMLKJIHGFEDCBA
QPONMLKJIH
GFEDCBA
*/
/*
QPONMLKJIHGFEDCBA
EDCBA
QPONM
LKJIHGF
*/
do { i = ((i>>(order_nr-10)) | ( i<<10 ) ) & ((1<<order_nr)-1); }
while ( i >= nr ); /* this won't ever loop if nr is a power of 2 */
return i;
}
static uint64_t tv_to_us(struct timeval *new)
{
return (new->tv_sec * 1000000) + new->tv_usec;
}
static uint64_t llgettimeofday(void)
{
struct timeval now;
gettimeofday(&now, NULL);
return tv_to_us(&now);
}
static uint64_t tv_delta(struct timeval *new, struct timeval *old)
{
return (((new->tv_sec - old->tv_sec)*1000000) +
(new->tv_usec - old->tv_usec));
}
#define RATE_IS_MAX() (0)
#define ratewrite(_io_fd, _buf, _n) write((_io_fd), (_buf), (_n))
#define initialize_mbit_rate()
static inline ssize_t write_exact(int fd, void *buf, size_t count)
{
return (write(fd, buf, count) == count);
}
static int print_stats(int xc_handle, uint32_t domid, int pages_sent,
xc_shadow_op_stats_t *stats, int print)
{
static struct timeval wall_last;
static long long d0_cpu_last;
static long long d1_cpu_last;
struct timeval wall_now;
long long wall_delta;
long long d0_cpu_now, d0_cpu_delta;
long long d1_cpu_now, d1_cpu_delta;
gettimeofday(&wall_now, NULL);
d0_cpu_now = xc_domain_get_cpu_usage(xc_handle, 0, /* FIXME */ 0)/1000;
d1_cpu_now = xc_domain_get_cpu_usage(xc_handle, domid, /* FIXME */ 0)/1000;
if ( (d0_cpu_now == -1) || (d1_cpu_now == -1) )
DPRINTF("ARRHHH!!\n");
wall_delta = tv_delta(&wall_now,&wall_last)/1000;
if ( wall_delta == 0 )
wall_delta = 1;
d0_cpu_delta = (d0_cpu_now - d0_cpu_last)/1000;
d1_cpu_delta = (d1_cpu_now - d1_cpu_last)/1000;
if ( print )
DPRINTF("delta %lldms, dom0 %d%%, target %d%%, sent %dMb/s, "
"dirtied %dMb/s %" PRId32 " pages\n",
wall_delta,
(int)((d0_cpu_delta*100)/wall_delta),
(int)((d1_cpu_delta*100)/wall_delta),
(int)((pages_sent*PAGE_SIZE)/(wall_delta*(1000/8))),
(int)((stats->dirty_count*PAGE_SIZE)/(wall_delta*(1000/8))),
stats->dirty_count);
d0_cpu_last = d0_cpu_now;
d1_cpu_last = d1_cpu_now;
wall_last = wall_now;
return 0;
}
static int analysis_phase(int xc_handle, uint32_t domid, int pfn_array_size,
unsigned long *arr, int runs)
{
long long start, now;
xc_shadow_op_stats_t stats;
int j;
start = llgettimeofday();
for ( j = 0; j < runs; j++ )
{
int i;
xc_shadow_control(xc_handle, domid, XEN_DOMCTL_SHADOW_OP_CLEAN,
arr, pfn_array_size, NULL, 0, NULL);
DPRINTF("#Flush\n");
for ( i = 0; i < 40; i++ )
{
usleep(50000);
now = llgettimeofday();
xc_shadow_control(xc_handle, domid, XEN_DOMCTL_SHADOW_OP_PEEK,
NULL, 0, NULL, 0, &stats);
DPRINTF("now= %lld faults= %"PRId32" dirty= %"PRId32"\n",
((now-start)+500)/1000,
stats.fault_count, stats.dirty_count);
}
}
return -1;
}
static int suspend_and_state(int (*suspend)(int), int xc_handle, int io_fd,
int dom, xc_dominfo_t *info,
vcpu_guest_context_t *ctxt)
{
int i = 0;
if ( !(*suspend)(dom) )
{
ERROR("Suspend request failed");
return -1;
}
retry:
if ( xc_domain_getinfo(xc_handle, dom, 1, info) != 1 )
{
ERROR("Could not get domain info");
return -1;
}
if ( xc_vcpu_getcontext(xc_handle, dom, 0, ctxt) )
ERROR("Could not get vcpu context");
if ( info->shutdown && (info->shutdown_reason == SHUTDOWN_suspend) )
return 0; /* success */
if ( info->paused )
{
/* Try unpausing domain, wait, and retest. */
xc_domain_unpause( xc_handle, dom );
ERROR("Domain was paused. Wait and re-test.");
usleep(10000); /* 10ms */
goto retry;
}
if ( ++i < 100 )
{
ERROR("Retry suspend domain.");
usleep(10000); /* 10ms */
goto retry;
}
ERROR("Unable to suspend domain.");
return -1;
}
int xc_hvm_save(int xc_handle, int io_fd, uint32_t dom, uint32_t max_iters,
uint32_t max_factor, uint32_t flags, int (*suspend)(int),
void *(*init_qemu_maps)(int, unsigned),
void (*qemu_flip_buffer)(int, int))
{
xc_dominfo_t info;
int rc = 1, i, j, last_iter, iter = 0;
int live = !!(flags & XCFLAGS_LIVE);
int debug = !!(flags & XCFLAGS_DEBUG);
int sent_last_iter, skip_this_iter;
/* The highest guest-physical frame number used by the current guest */
unsigned long max_pfn;
/* The size of an array big enough to contain all guest pfns */
unsigned long pfn_array_size;
/* Magic frames: ioreqs and xenstore comms. */
uint64_t magic_pfns[3]; /* ioreq_pfn, bufioreq_pfn, store_pfn */
/* A copy of the CPU context of the guest. */
vcpu_guest_context_t ctxt;
/* A table containg the PFNs (/not/ MFN!) to map. */
xen_pfn_t *pfn_batch = NULL;
/* A copy of hvm domain context buffer*/
uint32_t hvm_buf_size;
uint8_t *hvm_buf = NULL;
/* base of the region in which domain memory is mapped */
unsigned char *region_base = NULL;
uint32_t rec_size, nr_vcpus;
/* power of 2 order of pfn_array_size */
int order_nr;
/* bitmap of pages:
- that should be sent this iteration (unless later marked as skip);
- to skip this iteration because already dirty; */
unsigned long *to_send = NULL, *to_skip = NULL;
xc_shadow_op_stats_t stats;
unsigned long total_sent = 0;
uint64_t vcpumap = 1ULL;
DPRINTF("xc_hvm_save: dom=%d, max_iters=%d, max_factor=%d, flags=0x%x, "
"live=%d, debug=%d.\n", dom, max_iters, max_factor, flags,
live, debug);
/* If no explicit control parameters given, use defaults */
max_iters = max_iters ? : DEF_MAX_ITERS;
max_factor = max_factor ? : DEF_MAX_FACTOR;
initialize_mbit_rate();
if ( xc_domain_getinfo(xc_handle, dom, 1, &info) != 1 )
{
ERROR("HVM: Could not get domain info");
return 1;
}
nr_vcpus = info.nr_online_vcpus;
if ( mlock(&ctxt, sizeof(ctxt)) )
{
ERROR("HVM: Unable to mlock ctxt");
return 1;
}
/* Only have to worry about vcpu 0 even for SMP */
if ( xc_vcpu_getcontext(xc_handle, dom, 0, &ctxt) )
{
ERROR("HVM: Could not get vcpu context");
goto out;
}
DPRINTF("saved hvm domain info: max_memkb=0x%lx, nr_pages=0x%lx\n",
info.max_memkb, info.nr_pages);
if ( live )
{
/* Live suspend. Enable log-dirty mode. */
if ( xc_shadow_control(xc_handle, dom,
XEN_DOMCTL_SHADOW_OP_ENABLE_LOGDIRTY,
NULL, 0, NULL, 0, NULL) < 0 )
{
ERROR("Couldn't enable shadow mode");
goto out;
}
}
else
{
/* This is a non-live suspend. Suspend the domain .*/
if ( suspend_and_state(suspend, xc_handle, io_fd, dom, &info, &ctxt) )
{
ERROR("HVM Domain appears not to have suspended");
goto out;
}
}
last_iter = !live;
max_pfn = xc_memory_op(xc_handle, XENMEM_maximum_gpfn, &dom);
DPRINTF("after 1st handle hvm domain max_pfn=0x%lx, "
"max_memkb=0x%lx, live=%d.\n",
max_pfn, info.max_memkb, live);
/* Size of any array that covers 0 ... max_pfn */
pfn_array_size = max_pfn + 1;
if ( !write_exact(io_fd, &pfn_array_size, sizeof(unsigned long)) )
{
ERROR("Error when writing to state file (1)");
goto out;
}
/* pretend we sent all the pages last iteration */
sent_last_iter = pfn_array_size;
/* calculate the power of 2 order of pfn_array_size, e.g.
15->4 16->4 17->5 */
for ( i = pfn_array_size-1, order_nr = 0; i ; i >>= 1, order_nr++ )
continue;
/* Setup to_send / to_fix and to_skip bitmaps */
to_send = malloc(BITMAP_SIZE);
to_skip = malloc(BITMAP_SIZE);
if ( live )
{
/* Get qemu-dm logging dirty pages too */
void *seg = init_qemu_maps(dom, BITMAP_SIZE);
qemu_bitmaps[0] = seg;
qemu_bitmaps[1] = seg + BITMAP_SIZE;
qemu_active = 0;
qemu_non_active = 1;
}
hvm_buf_size = xc_domain_hvm_getcontext(xc_handle, dom, 0, 0);
if ( hvm_buf_size == -1 )
{
ERROR("Couldn't get HVM context size from Xen");
goto out;
}
hvm_buf = malloc(hvm_buf_size);
if ( !to_send || !to_skip || !hvm_buf )
{
ERROR("Couldn't allocate memory");
goto out;
}
memset(to_send, 0xff, BITMAP_SIZE);
if ( lock_pages(to_send, BITMAP_SIZE) )
{
ERROR("Unable to lock to_send");
return 1;
}
/* (to fix is local only) */
if ( lock_pages(to_skip, BITMAP_SIZE) )
{
ERROR("Unable to lock to_skip");
return 1;
}
analysis_phase(xc_handle, dom, pfn_array_size, to_skip, 0);
/* We want zeroed memory so use calloc rather than malloc. */
pfn_batch = calloc(MAX_BATCH_SIZE, sizeof(*pfn_batch));
if ( pfn_batch == NULL )
{
ERROR("failed to alloc memory for pfn_batch array");
errno = ENOMEM;
goto out;
}
for ( ; ; )
{
unsigned int prev_pc, sent_this_iter, N, batch;
iter++;
sent_this_iter = 0;
skip_this_iter = 0;
prev_pc = 0;
N=0;
DPRINTF("Saving memory pages: iter %d 0%%", iter);
while ( N < pfn_array_size )
{
unsigned int this_pc = (N * 100) / pfn_array_size;
int rc;
if ( (this_pc - prev_pc) >= 5 )
{
DPRINTF("\b\b\b\b%3d%%", this_pc);
prev_pc = this_pc;
}
if ( !last_iter )
{
/* Slightly wasteful to peek the whole array evey time,
but this is fast enough for the moment. */
rc = xc_shadow_control(
xc_handle, dom, XEN_DOMCTL_SHADOW_OP_PEEK, to_skip,
pfn_array_size, NULL, 0, NULL);
if ( rc != pfn_array_size )
{
ERROR("Error peeking shadow bitmap");
goto out;
}
}
/* load pfn_batch[] with the mfn of all the pages we're doing in
this batch. */
for ( batch = 0;
(batch < MAX_BATCH_SIZE) && (N < pfn_array_size);
N++ )
{
int n = permute(N, pfn_array_size, order_nr);
if ( 0 && debug )
DPRINTF("%d pfn= %08lx %d \n",
iter, (unsigned long)n, test_bit(n, to_send));
if ( !last_iter &&
test_bit(n, to_send) &&
test_bit(n, to_skip) )
skip_this_iter++; /* stats keeping */
if ( !((test_bit(n, to_send) && !test_bit(n, to_skip)) ||
(test_bit(n, to_send) && last_iter)) )
continue;
/* Skip PFNs that aren't really there */
if ( (n >= 0xa0 && n < 0xc0) /* VGA hole */
|| (n >= (HVM_BELOW_4G_MMIO_START >> PAGE_SHIFT) &&
n < (1ULL << 32) >> PAGE_SHIFT) /* 4G MMIO hole */ )
continue;
/*
** we get here if:
** 1. page is marked to_send & hasn't already been re-dirtied
** 2. (ignore to_skip in last iteration)
*/
pfn_batch[batch] = n;
batch++;
}
if ( batch == 0 )
goto skip; /* vanishingly unlikely... */
region_base = xc_map_foreign_batch(
xc_handle, dom, PROT_READ, pfn_batch, batch);
if ( region_base == 0 )
{
ERROR("map batch failed");
goto out;
}
/* write num of pfns */
if ( !write_exact(io_fd, &batch, sizeof(unsigned int)) )
{
ERROR("Error when writing to state file (2)");
goto out;
}
/* write all the pfns */
if ( !write_exact(io_fd, pfn_batch, sizeof(unsigned long)*batch) )
{
ERROR("Error when writing to state file (3)");
goto out;
}
for ( j = 0; j < batch; j++ )
{
if ( pfn_batch[j] & XEN_DOMCTL_PFINFO_LTAB_MASK )
continue;
if ( ratewrite(io_fd, region_base + j*PAGE_SIZE,
PAGE_SIZE) != PAGE_SIZE )
{
ERROR("ERROR when writing to state file (4)");
goto out;
}
}
sent_this_iter += batch;
munmap(region_base, batch*PAGE_SIZE);
} /* end of this while loop for this iteration */
skip:
total_sent += sent_this_iter;
DPRINTF("\r %d: sent %d, skipped %d, ",
iter, sent_this_iter, skip_this_iter );
if ( last_iter )
{
print_stats( xc_handle, dom, sent_this_iter, &stats, 1);
DPRINTF("Total pages sent= %ld (%.2fx)\n",
total_sent, ((float)total_sent)/pfn_array_size );
}
if ( last_iter && debug )
{
int minusone = -1;
memset(to_send, 0xff, BITMAP_SIZE);
debug = 0;
DPRINTF("Entering debug resend-all mode\n");
/* send "-1" to put receiver into debug mode */
if ( !write_exact(io_fd, &minusone, sizeof(int)) )
{
ERROR("Error when writing to state file (6)");
goto out;
}
continue;
}
if ( last_iter )
break;
if ( live )
{
if ( ((sent_this_iter > sent_last_iter) && RATE_IS_MAX()) ||
(iter >= max_iters) ||
(sent_this_iter+skip_this_iter < 50) ||
(total_sent > pfn_array_size*max_factor) )
{
DPRINTF("Start last iteration for HVM domain\n");
last_iter = 1;
if ( suspend_and_state(suspend, xc_handle, io_fd, dom, &info,
&ctxt))
{
ERROR("Domain appears not to have suspended");
goto out;
}
DPRINTF("SUSPEND eip %08lx edx %08lx\n",
(unsigned long)ctxt.user_regs.eip,
(unsigned long)ctxt.user_regs.edx);
}
if ( xc_shadow_control(xc_handle, dom,
XEN_DOMCTL_SHADOW_OP_CLEAN, to_send,
pfn_array_size, NULL,
0, &stats) != pfn_array_size )
{
ERROR("Error flushing shadow PT");
goto out;
}
/* Pull in the dirty bits from qemu too */
if ( !last_iter )
{
qemu_active = qemu_non_active;
qemu_non_active = qemu_active ? 0 : 1;
qemu_flip_buffer(dom, qemu_active);
for ( j = 0; j < BITMAP_SIZE / sizeof(unsigned long); j++ )
{
to_send[j] |= qemu_bitmaps[qemu_non_active][j];
qemu_bitmaps[qemu_non_active][j] = 0;
}
}
else
{
for ( j = 0; j < BITMAP_SIZE / sizeof(unsigned long); j++ )
to_send[j] |= qemu_bitmaps[qemu_active][j];
}
sent_last_iter = sent_this_iter;
print_stats(xc_handle, dom, sent_this_iter, &stats, 1);
}
} /* end of while 1 */
DPRINTF("All HVM memory is saved\n");
{
struct {
int minustwo;
int max_vcpu_id;
uint64_t vcpumap;
} chunk = { -2, info.max_vcpu_id };
if (info.max_vcpu_id >= 64) {
ERROR("Too many VCPUS in guest!");
goto out;
}
for (i = 1; i <= info.max_vcpu_id; i++) {
xc_vcpuinfo_t vinfo;
if ((xc_vcpu_getinfo(xc_handle, dom, i, &vinfo) == 0) &&
vinfo.online)
vcpumap |= 1ULL << i;
}
chunk.vcpumap = vcpumap;
if(!write_exact(io_fd, &chunk, sizeof(chunk))) {
ERROR("Error when writing to state file (errno %d)", errno);
goto out;
}
}
/* Zero terminate */
i = 0;
if ( !write_exact(io_fd, &i, sizeof(int)) )
{
ERROR("Error when writing to state file (6)");
goto out;
}
/* Save magic-page locations. */
memset(magic_pfns, 0, sizeof(magic_pfns));
xc_get_hvm_param(xc_handle, dom, HVM_PARAM_IOREQ_PFN,
(unsigned long *)&magic_pfns[0]);
xc_get_hvm_param(xc_handle, dom, HVM_PARAM_BUFIOREQ_PFN,
(unsigned long *)&magic_pfns[1]);
xc_get_hvm_param(xc_handle, dom, HVM_PARAM_STORE_PFN,
(unsigned long *)&magic_pfns[2]);
if ( !write_exact(io_fd, magic_pfns, sizeof(magic_pfns)) )
{
ERROR("Error when writing to state file (7)");
goto out;
}
/* save vcpu/vmcs contexts */
for ( i = 0; i < nr_vcpus; i++ )
{
if ( !(vcpumap & (1ULL << i)) )
continue;
if ( xc_vcpu_getcontext(xc_handle, dom, i, &ctxt) )
{
ERROR("HVM:Could not get vcpu context");
goto out;
}
DPRINTF("write vcpu %d context.\n", i);
if ( !write_exact(io_fd, &(ctxt), sizeof(ctxt)) )
{
ERROR("write vcpu context failed!\n");
goto out;
}
}
if ( (rec_size = xc_domain_hvm_getcontext(xc_handle, dom, hvm_buf,
hvm_buf_size)) == -1 )
{
ERROR("HVM:Could not get hvm buffer");
goto out;
}
if ( !write_exact(io_fd, &rec_size, sizeof(uint32_t)) )
{
ERROR("error write hvm buffer size");
goto out;
}
if ( !write_exact(io_fd, hvm_buf, rec_size) )
{
ERROR("write HVM info failed!\n");
goto out;
}
/* Success! */
rc = 0;
out:
if ( live )
{
if ( xc_shadow_control(xc_handle, dom, XEN_DOMCTL_SHADOW_OP_OFF,
NULL, 0, NULL, 0, NULL) < 0 )
DPRINTF("Warning - couldn't disable shadow mode");
}
free(hvm_buf);
free(pfn_batch);
free(to_send);
free(to_skip);
return !!rc;
}