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. >