From: Andres Lagar-Cavilla <andres@lagarcavilla.org>
To: xen-devel@lists.xensource.com
Cc: olaf@aepfle.de, tim@xen.org, andres@gridcentric.ca, adin@gridcentric.ca
Subject: [PATCH] Testing for mem event ring management
Date: Thu, 12 Jan 2012 11:41:41 -0500 [thread overview]
Message-ID: <330baa2bb903e24d67b7.1326386501@xdev.gridcentric.ca> (raw)
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:
+ */
next reply other threads:[~2012-01-12 16:41 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2012-01-12 16:41 Andres Lagar-Cavilla [this message]
2012-02-20 16:13 ` [PATCH] Testing for mem event ring management Ian Jackson
2012-02-20 19:57 ` Andres Lagar-Cavilla
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=330baa2bb903e24d67b7.1326386501@xdev.gridcentric.ca \
--to=andres@lagarcavilla.org \
--cc=adin@gridcentric.ca \
--cc=andres@gridcentric.ca \
--cc=olaf@aepfle.de \
--cc=tim@xen.org \
--cc=xen-devel@lists.xensource.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.