tools/xenpaging/Makefile | 5 +-
tools/xenpaging/memoper.c | 627 ++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 631 insertions(+), 1 deletions(-)
This is example code, not meant for tree consumption. So
that others can run tests on the ring management bits.
The testing works as follows:
1. start off a Linux HVM with 4 vcpus
2. ./memoper <domid>
(recommended to run memoper inside a screen -L session as it tends
to output a lot of stuff)
3. xl unpause <domid>
4. fireworks
To really drive the ring (assuming 1GB guest RAM and four vcpus):
6. on the domain console, remount tmpfs to /dev/shm (or wherever) with
size sufficient to consume most of the guest''s RAM (e.g.
mount -o remount,size=1000000000)
7. on the domain console
for i in `seq 4`; do (dd if=/dev/zero of=/dev/shm/file.$i bs=1k
count=255000)& done
More fireworks!
diff -r 24f43fcefc12 -r 330baa2bb903 tools/xenpaging/Makefile
--- a/tools/xenpaging/Makefile
+++ b/tools/xenpaging/Makefile
@@ -15,10 +15,13 @@ CFLAGS += -Wno-unused
CFLAGS += -g
OBJS = $(SRCS:.c=.o)
-IBINS = xenpaging
+IBINS = xenpaging memoper
all: $(IBINS)
+memoper: memoper.o
+ $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
+
xenpaging: $(OBJS)
$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS) $(APPEND_LDFLAGS)
diff -r 24f43fcefc12 -r 330baa2bb903 tools/xenpaging/memoper.c
--- /dev/null
+++ b/tools/xenpaging/memoper.c
@@ -0,0 +1,627 @@
+/******************************************************************************
+ * tools/xenpaging/memoper.c
+ *
+ * Test tool for log memory accesses by guest.
+ * Copyright (c) 2011 by GridCentric, Inc. (Andres Lagar-Cavilla)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 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
+ */
+
+#define _XOPEN_SOURCE 600
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <time.h>
+#include <signal.h>
+#include <unistd.h>
+#include <poll.h>
+#include <xc_private.h>
+#include <xs.h>
+#include "xenpaging.h"
+#include "xc_bitops.h"
+
+#define ROUNDUP(_x,_w) (((unsigned long)(_x)+(1UL<<(_w))-1) &
~((1UL<<(_w))-1))
+#define NRPAGES(x) (ROUNDUP(x, PAGE_SHIFT) >> PAGE_SHIFT)
+
+/* Whether we signal resume to the hypervisor via domctl or event channel */
+#define USE_EVTCHN_DOMCTL 0
+
+typedef struct memaccess_s {
+ xc_interface *xc_handle;
+ struct xs_handle *xs_handle;
+
+ xc_domaininfo_t *domain_info;
+
+ mem_event_t mem_event;
+} memaccess_t;
+
+static char watch_token[16];
+static int interrupted;
+DECLARE_HYPERCALL_BUFFER(unsigned long, dirty_bitmap);
+
+#undef DPRINTF
+#define DPRINTF(_f, _a ...) fprintf(stderr, _f, ##_a)
+
+static void close_handler(int sig)
+{
+ interrupted = sig;
+}
+
+static int wait_for_event_or_timeout(memaccess_t *memaccess)
+{
+ xc_interface *xch = memaccess->xc_handle;
+ xc_evtchn *xce = memaccess->mem_event.xce_handle;
+ char **vec;
+ unsigned int num;
+ struct pollfd fd[2];
+ int port;
+ int rc;
+
+ /* Wait for event channel and xenstore */
+ fd[0].fd = xc_evtchn_fd(xce);
+ fd[0].events = POLLIN | POLLERR;
+ fd[1].fd = xs_fileno(memaccess->xs_handle);
+ fd[1].events = POLLIN | POLLERR;
+
+ rc = poll(fd, 2, 100);
+ if ( rc < 0 )
+ {
+ if (errno == EINTR)
+ return 0;
+
+ ERROR("Poll exited with an error");
+ return -errno;
+ }
+
+ /* First check for guest shutdown */
+ if ( rc && fd[1].revents & POLLIN )
+ {
+ DPRINTF("Got event from xenstore\n");
+ vec = xs_read_watch(memaccess->xs_handle, &num);
+ if ( vec )
+ {
+ if ( strcmp(vec[XS_WATCH_TOKEN], watch_token) == 0 )
+ {
+ /* If our guest disappeared, set interrupt flag and fall
through */
+ if ( xs_is_domain_introduced(memaccess->xs_handle,
memaccess->mem_event.domain_id) == false )
+ {
+ xs_unwatch(memaccess->xs_handle,
"@releaseDomain", watch_token);
+ interrupted = SIGQUIT;
+ rc = 0;
+ }
+ }
+ free(vec);
+ }
+ }
+
+ if ( rc && fd[0].revents & POLLIN )
+ {
+ int rv;
+ DPRINTF("Got event from evtchn\n");
+ port = xc_evtchn_pending(xce);
+ if ( port == -1 )
+ {
+ ERROR("Failed to read port from event channel");
+ rc = -1;
+ goto err;
+ }
+
+ rv = xc_evtchn_unmask(xce, port);
+ if ( rv < 0 )
+ {
+ ERROR("Failed to unmask event channel port");
+ }
+ }
+err:
+ return rc;
+}
+
+static void *init_page(void)
+{
+ void *buffer;
+ int ret;
+
+ /* Allocated page memory */
+ ret = posix_memalign(&buffer, PAGE_SIZE, PAGE_SIZE);
+ if ( ret != 0 )
+ goto out_alloc;
+
+ /* Lock buffer in memory so it can''t be paged out */
+ ret = mlock(buffer, PAGE_SIZE);
+ if ( ret != 0 )
+ goto out_lock;
+
+ return buffer;
+
+ out_init:
+ munlock(buffer, PAGE_SIZE);
+ out_lock:
+ free(buffer);
+ out_alloc:
+ return NULL;
+}
+
+static memaccess_t *access_init(domid_t domain_id)
+{
+ memaccess_t *memaccess;
+ xc_interface *xch;
+ xentoollog_logger *dbg = NULL;
+ char *p;
+ int rc;
+
+ xch = xc_interface_open(dbg, NULL, 0);
+ if ( !xch )
+ goto err_iface;
+
+ DPRINTF("access init\n");
+
+ /* Allocate memory */
+ memaccess = malloc(sizeof(memaccess_t));
+ memset(memaccess, 0, sizeof(memaccess_t));
+
+ /* Open connection to xenstore */
+ memaccess->xs_handle = xs_open(0);
+ if ( memaccess->xs_handle == NULL )
+ {
+ ERROR("Error initialising xenstore connection");
+ goto err;
+ }
+
+ /* write domain ID to watch so we can ignore other domain shutdowns */
+ snprintf(watch_token, sizeof(watch_token), "%u", domain_id);
+ if ( xs_watch(memaccess->xs_handle, "@releaseDomain",
watch_token) == false )
+ {
+ ERROR("Could not bind to shutdown watch\n");
+ goto err;
+ }
+
+ /* Open connection to xen */
+ memaccess->xc_handle = xch;
+
+ /* Set domain id */
+ memaccess->mem_event.domain_id = domain_id;
+
+ /* Initialise shared page */
+ memaccess->mem_event.shared_page = init_page();
+ if ( memaccess->mem_event.shared_page == NULL )
+ {
+ ERROR("Error initialising shared page");
+ goto err;
+ }
+
+ /* Initialise ring page */
+ memaccess->mem_event.ring_page = init_page();
+ if ( memaccess->mem_event.ring_page == NULL )
+ {
+ ERROR("Error initialising ring page");
+ goto err;
+ }
+
+ /* Initialise ring */
+ SHARED_RING_INIT((mem_event_sring_t *)memaccess->mem_event.ring_page);
+ BACK_RING_INIT(&memaccess->mem_event.back_ring,
+ (mem_event_sring_t *)memaccess->mem_event.ring_page,
+ PAGE_SIZE);
+
+ /* Initialise Xen */
+ rc = xc_mem_access_enable(xch, memaccess->mem_event.domain_id,
+ memaccess->mem_event.shared_page,
+ memaccess->mem_event.ring_page);
+ if ( rc != 0 )
+ {
+ switch ( errno ) {
+ case EBUSY:
+ ERROR("access is (or was) active on this domain");
+ break;
+ case ENODEV:
+ ERROR("EPT not supported for this guest");
+ break;
+ case EXDEV:
+ ERROR("access is not supported in a PoD guest");
+ break;
+ default:
+ ERROR("Error initialising shared page: %s",
strerror(errno));
+ break;
+ }
+ goto err;
+ }
+
+ /* Open event channel */
+ memaccess->mem_event.xce_handle = xc_evtchn_open(NULL, 0);
+ if ( memaccess->mem_event.xce_handle == NULL )
+ {
+ ERROR("Failed to open event channel");
+ goto err;
+ }
+
+ /* Bind event notification */
+ rc = xc_evtchn_bind_interdomain(memaccess->mem_event.xce_handle,
+ memaccess->mem_event.domain_id,
+
memaccess->mem_event.shared_page->port);
+ if ( rc < 0 )
+ {
+ ERROR("Failed to bind event channel");
+ goto err;
+ }
+
+ memaccess->mem_event.port = rc;
+
+ /* Get domaininfo */
+ memaccess->domain_info = malloc(sizeof(xc_domaininfo_t));
+ if ( memaccess->domain_info == NULL )
+ {
+ ERROR("Error allocating memory for domain info");
+ goto err;
+ }
+
+ rc = xc_domain_getinfolist(xch, memaccess->mem_event.domain_id, 1,
+ memaccess->domain_info);
+ if ( rc != 1 )
+ {
+ ERROR("Error getting domain info");
+ goto err;
+ }
+ DPRINTF("max_pages = %"PRIx64"\n",
memaccess->domain_info->max_pages);
+
+ return memaccess;
+
+ err:
+ if ( memaccess )
+ {
+ if ( memaccess->xs_handle )
+ xs_close(memaccess->xs_handle);
+ xc_interface_close(xch);
+ if ( memaccess->mem_event.shared_page )
+ {
+ munlock(memaccess->mem_event.shared_page, PAGE_SIZE);
+ free(memaccess->mem_event.shared_page);
+ }
+
+ if ( memaccess->mem_event.ring_page )
+ {
+ munlock(memaccess->mem_event.ring_page, PAGE_SIZE);
+ free(memaccess->mem_event.ring_page);
+ }
+
+ free(memaccess->domain_info);
+ free(memaccess);
+ }
+
+ err_iface:
+ return NULL;
+}
+
+static int brick_domain(memaccess_t *memaccess, int *p2m_size)
+{
+ int max, rc;
+ unsigned long mb;
+ xc_interface *xch = memaccess->xc_handle;
+
+ (void)xc_domain_pause(xch, memaccess->mem_event.domain_id);
+
+ max = xc_domain_maximum_gpfn(xch, memaccess->mem_event.domain_id);
+ if (max <= 0) {
+ fprintf(stderr, "MAX GPFN WEIRDO %d\n", max);
+ return 1;
+ }
+ fprintf(stderr, "GPFN MAX %x\n", max);
+ *p2m_size = max;
+
+ dirty_bitmap = xc_hypercall_buffer_alloc_pages(memaccess->xc_handle,
+ dirty_bitmap, NRPAGES(bitmap_size(*p2m_size)));
+ if ( !dirty_bitmap )
+ {
+ ERROR("ERROR for bitmap %d %d\n", *p2m_size, errno);
+ return 1;
+ }
+ /* No need to memset, pages are memset by libxc */
+
+ mb = 64;/* TUNABLE!?!? */
+ if ( (rc = xc_shadow_control(xch, memaccess->mem_event.domain_id,
+ XEN_DOMCTL_SHADOW_OP_SET_ALLOCATION,
+ NULL, 0, &mb, 0, NULL)) < 0 )
+ {
+ fprintf(stderr, "COULD NOT ALLOCATE SHADOW RAM FOR BITMAP "
+ "rc %d errno %d\n", rc, errno);
+ return 1;
+ }
+
+ rc = xc_shadow_control(xch, memaccess->mem_event.domain_id,
+ XEN_DOMCTL_SHADOW_OP_ENABLE_LOGDIRTY,
+ NULL, 0, NULL, 0, NULL);
+
+ if ( rc < 0 )
+ {
+ fprintf(stderr, "Couldn''t enable shadow mode (rc %d)
"
+ "(errno %d)\n", rc, errno );
+ /* log-dirty already enabled? There''s no test op,
+ so attempt to disable then reenable it */
+ rc = xc_shadow_control(xch, memaccess->mem_event.domain_id,
+ XEN_DOMCTL_SHADOW_OP_OFF,
+ NULL, 0, NULL, 0, NULL);
+ fprintf(stderr, "Disabling shadow log dirty yielded rc %d errno
"
+ "%d\n", rc, errno);
+ rc = xc_shadow_control(xch, memaccess->mem_event.domain_id,
+ XEN_DOMCTL_SHADOW_OP_ENABLE_LOGDIRTY,
+ NULL, 0, NULL, 0, NULL);
+
+ if ( rc < 0 )
+ {
+ fprintf(stderr, "Couldn''t enable shadow mode (rc %d)
"
+ "(errno %d)\n", rc, errno );
+ return 1;
+ }
+ } else {
+
+ errno = 0;
+ rc = xc_shadow_control(xch, memaccess->mem_event.domain_id,
+ XEN_DOMCTL_SHADOW_OP_CLEAN,
+ NULL, *p2m_size, NULL, 0, NULL);
+ fprintf(stderr, "SHADOW CLEAN RC %x errno %d\n", rc, errno);
+ }
+
+ rc = xc_hvm_set_mem_access(xch, memaccess->mem_event.domain_id,
+ HVMMEM_access_n2rwx, 0, (uint64_t) *p2m_size);
+ if (rc) {
+ fprintf(stderr, "MEMTYPE RC %d %d\n", rc, errno);
+ if ( errno != ENOMEM )
+ /* We get ENOMEM when trying to set access rights on gfn''s that
+ * have _never_ been touched, to the extent the p2m doesn''t
+ * even know about them. This is extremely unlikely to happen
+ * for pages that the VM will end up actually accessing, for the
+ * the physmap is fully populated (pod, paged, shared, normal,
+ * whatever, populated nonetheless) up front.*/
+ return 1;
+ }
+
+ return 0;
+}
+
+static int access_teardown(memaccess_t *memaccess)
+{
+ int rc;
+ xc_interface *xch;
+
+ if ( memaccess == NULL )
+ return 0;
+
+ xch = memaccess->xc_handle;
+ memaccess->xc_handle = NULL;
+ /* Tear down domain access in Xen */
+ rc = xc_mem_access_disable(xch, memaccess->mem_event.domain_id);
+ if ( rc != 0 )
+ {
+ ERROR("Error tearing down domain access in xen");
+ }
+
+ /* Unbind VIRQ */
+ rc = xc_evtchn_unbind(memaccess->mem_event.xce_handle,
memaccess->mem_event.port);
+ if ( rc != 0 )
+ {
+ ERROR("Error unbinding event port");
+ }
+ memaccess->mem_event.port = -1;
+
+ /* Close event channel */
+ rc = xc_evtchn_close(memaccess->mem_event.xce_handle);
+ if ( rc != 0 )
+ {
+ ERROR("Error closing event channel");
+ }
+ memaccess->mem_event.xce_handle = NULL;
+
+ /* Close connection to xenstore */
+ xs_close(memaccess->xs_handle);
+
+ /* Close connection to Xen */
+ rc = xc_interface_close(xch);
+ if ( rc != 0 )
+ {
+ ERROR("Error closing connection to xen");
+ }
+
+ return 0;
+
+ err:
+ return -1;
+}
+
+static void get_request(mem_event_t *mem_event, mem_event_request_t *req)
+{
+ mem_event_back_ring_t *back_ring;
+ RING_IDX req_cons;
+
+ back_ring = &mem_event->back_ring;
+ req_cons = back_ring->req_cons;
+
+ /* Copy request */
+ memcpy(req, RING_GET_REQUEST(back_ring, req_cons), sizeof(*req));
+ req_cons++;
+
+ /* Update ring */
+ back_ring->req_cons = req_cons;
+ back_ring->sring->req_event = req_cons + 1;
+}
+
+static void put_response(mem_event_t *mem_event, mem_event_response_t *rsp)
+{
+ mem_event_back_ring_t *back_ring;
+ RING_IDX rsp_prod;
+
+ back_ring = &mem_event->back_ring;
+ rsp_prod = back_ring->rsp_prod_pvt;
+
+ /* Copy response */
+ memcpy(RING_GET_RESPONSE(back_ring, rsp_prod), rsp, sizeof(*rsp));
+ rsp_prod++;
+
+ /* Update ring */
+ back_ring->rsp_prod_pvt = rsp_prod;
+ RING_PUSH_RESPONSES(back_ring);
+}
+
+static int resume_page(memaccess_t *memaccess, mem_event_response_t *rsp, int
notify_policy)
+{
+ int ret;
+
+ /* Put the page info on the ring */
+ put_response(&memaccess->mem_event, rsp);
+
+#if USE_EVTCHN_DOMCTL
+ /* Tell Xen page is ready */
+ ret = xc_mem_access_resume(memaccess->xc_handle,
memaccess->mem_event.domain_id,
+ rsp->gfn);
+ if ( ret == 0 )
+#endif
+ ret = xc_evtchn_notify(memaccess->mem_event.xce_handle,
+ memaccess->mem_event.port);
+
+ out:
+ return ret;
+}
+
+
+int main(int argc, char *argv[])
+{
+ struct sigaction act;
+ memaccess_t *memaccess;
+ mem_event_request_t req;
+ mem_event_response_t rsp;
+ unsigned long i;
+ int rc = -1;
+ int rc1, frc, p2m_size;
+ xc_interface *xch;
+
+ /* Initialise domain access */
+ memaccess = access_init(atoi(argv[1]));
+ if ( memaccess == NULL )
+ {
+ fprintf(stderr, "Error initialising access");
+ return 1;
+ }
+ xch = memaccess->xc_handle;
+
+ if ( brick_domain(memaccess, &p2m_size) )
+ {
+ int rv;
+ fprintf(stderr, "Error bricking domain\n");
+ if ( (rv = access_teardown(memaccess)) )
+ fprintf(stderr, "Further, error %d errno %d when "
+ "tearing down\n", rv, errno);
+ return 1;
+ }
+
+ DPRINTF("starting %s %u\n", argv[0],
memaccess->mem_event.domain_id);
+
+ /* ensure that if we get a signal, we''ll do cleanup, then exit */
+ act.sa_handler = close_handler;
+ act.sa_flags = 0;
+ sigemptyset(&act.sa_mask);
+ sigaction(SIGHUP, &act, NULL);
+ sigaction(SIGTERM, &act, NULL);
+ sigaction(SIGINT, &act, NULL);
+ sigaction(SIGALRM, &act, NULL);
+
+ /* Swap pages in and out */
+ while ( 1 )
+ {
+ /* Wait for Xen to signal that a page needs paged in */
+#ifdef SLEEP_TEST_RING
+ /* SLEEP FOR TEST */ usleep(1000*1000*10);
+#endif
+ rc = wait_for_event_or_timeout(memaccess);
+ if ( rc < 0 )
+ {
+ ERROR("Error getting event");
+ goto out;
+ }
+ else if ( rc != 0 )
+ {
+ DPRINTF("Got event from Xen\n");
+ }
+
+ while (
RING_HAS_UNCONSUMED_REQUESTS(&memaccess->mem_event.back_ring) )
+ {
+ get_request(&memaccess->mem_event, &req);
+
+ DPRINTF("page accessed (domain = %d; vcpu = %d;"
+ " gfn = %"PRIx64"; paused = %d)
%c%c%c\n",
+ memaccess->mem_event.domain_id, req.vcpu_id, req.gfn,
+ !!(req.flags & MEM_EVENT_FLAG_VCPU_PAUSED),
+ req.access_r ? ''R'' : ''-'',
+ req.access_w ? ''W'' : ''-'',
+ req.access_x ? ''X'' : ''-'');
+
+ /* Tell Xen we consumed the event so it doesn''t stall */
+ /* Prepare the response */
+ rsp.gfn = req.gfn;
+ rsp.vcpu_id = req.vcpu_id;
+ rsp.flags = req.flags;
+
+ rc = resume_page(memaccess, &rsp, 0);
+ if ( rc != 0 )
+ {
+ ERROR("Error resuming");
+ goto out;
+ }
+ }
+
+ /* Exit on any signal */
+ if ( interrupted )
+ break;
+ }
+ DPRINTF("%s got signal %d\n", argv[0], interrupted);
+
+ frc = xc_shadow_control(
+ memaccess->xc_handle, memaccess->mem_event.domain_id,
+ XEN_DOMCTL_SHADOW_OP_CLEAN, HYPERCALL_BUFFER(dirty_bitmap),
+ p2m_size, NULL, 0, NULL);
+ if ( frc != p2m_size )
+ {
+ ERROR("Error peeking shadow bitmap");
+ xc_hypercall_buffer_free_pages(memaccess->xc_handle, dirty_bitmap,
+ NRPAGES(bitmap_size(p2m_size)));
+ goto out;
+ }
+
+ for ( i = 0; i < p2m_size; i++)
+ if (test_bit(i, dirty_bitmap))
+ DPRINTF("GFN %lx DIRTY\n", i);
+
+ (void)xc_hypercall_buffer_free_pages(memaccess->xc_handle, dirty_bitmap,
+ NRPAGES(bitmap_size(p2m_size)));
+
+ out:
+
+ /* Tear down */
+ rc1 = access_teardown(memaccess);
+ if ( rc1 != 0 )
+ fprintf(stderr, "Error tearing down\n");
+
+ if ( rc == 0 )
+ rc = rc1;
+ return rc;
+}
+
+
+/*
+ * Local variables:
+ * mode: C
+ * c-set-style: "BSD"
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
Andres Lagar-Cavilla writes ("[Xen-devel] [PATCH] Testing for mem event
ring management"):> This is example code, not meant for tree consumption. So
> that others can run tests on the ring management bits.
Is this supposed to apply to tip, and build ?
memoper.c:48: error: expected specifier-qualifier-list before
''mem_event_t''
memoper.c: In function ''wait_for_event_or_timeout'':
memoper.c:66: error: ''memaccess_t'' has no member named
''mem_event''
memoper.c:99: error: ''memaccess_t'' has no member named
''mem_event''
...
Ian.
Andres Lagar-Cavilla
2012-Feb-20 19:57 UTC
Re: [PATCH] Testing for mem event ring management
> Andres Lagar-Cavilla writes ("[Xen-devel] [PATCH] Testing for mem event > ring management"): >> This is example code, not meant for tree consumption. So >> that others can run tests on the ring management bits. > > Is this supposed to apply to tip, and build ?It was, way back when. This isn''t a significant diff from tests/xen-access either -- Effecitvely, it just sets a different mem_access type (n2rwx, instead of rw2rx) Andres> > memoper.c:48: error: expected specifier-qualifier-list before > ''mem_event_t'' > memoper.c: In function ''wait_for_event_or_timeout'': > memoper.c:66: error: ''memaccess_t'' has no member named ''mem_event'' > memoper.c:99: error: ''memaccess_t'' has no member named ''mem_event'' > ... > > Ian. >