aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Brook <paul@codesourcery.com>2010-05-29 02:27:35 +0100
committerPaul Brook <paul@codesourcery.com>2010-05-29 02:27:35 +0100
commit68a1c816868b3e35a1da698af412b29e61b1948a (patch)
tree2f8ae184dd481d3a0e74d1de76fc7b53189fd97c
parent0be1d07c0e4fa7395aca5129cfc5bb9a0a94a78c (diff)
Pre-allocate guest address space
Allow pre-allocation of the guest virtual address space in usermode emulation. Signed-off-by: Paul Brook <paul@codesourcery.com>
-rw-r--r--cpu-all.h1
-rw-r--r--linux-user/elfload.c2
-rw-r--r--linux-user/main.c63
-rw-r--r--linux-user/mmap.c120
-rw-r--r--qemu-doc.texi5
5 files changed, 183 insertions, 8 deletions
diff --git a/cpu-all.h b/cpu-all.h
index 47a5722a1d..77eaf85735 100644
--- a/cpu-all.h
+++ b/cpu-all.h
@@ -627,6 +627,7 @@ static inline void stfq_be_p(void *ptr, float64 v)
#if defined(CONFIG_USE_GUEST_BASE)
extern unsigned long guest_base;
extern int have_guest_base;
+extern unsigned long reserved_va;
#define GUEST_BASE guest_base
#else
#define GUEST_BASE 0ul
diff --git a/linux-user/elfload.c b/linux-user/elfload.c
index 1f27918beb..2d920f2017 100644
--- a/linux-user/elfload.c
+++ b/linux-user/elfload.c
@@ -1682,7 +1682,7 @@ int load_elf_binary(struct linux_binprm * bprm, struct target_pt_regs * regs,
* In case where user has not explicitly set the guest_base, we
* probe here that should we set it automatically.
*/
- if (!have_guest_base) {
+ if (!(have_guest_base || reserved_va)) {
/*
* Go through ELF program header table and find the address
* range used by loadable segments. Check that this is available on
diff --git a/linux-user/main.c b/linux-user/main.c
index de1076b0af..0f23fc9cd9 100644
--- a/linux-user/main.c
+++ b/linux-user/main.c
@@ -44,6 +44,7 @@ unsigned long mmap_min_addr;
#if defined(CONFIG_USE_GUEST_BASE)
unsigned long guest_base;
int have_guest_base;
+unsigned long reserved_va;
#endif
static const char *interp_prefix = CONFIG_QEMU_PREFIX;
@@ -2610,6 +2611,7 @@ static void usage(void)
"-0 argv0 forces target process argv[0] to be argv0\n"
#if defined(CONFIG_USE_GUEST_BASE)
"-B address set guest_base address to address\n"
+ "-R size reserve size bytes for guest virtual address space\n"
#endif
"\n"
"Debug options:\n"
@@ -2805,6 +2807,39 @@ int main(int argc, char **argv, char **envp)
} else if (!strcmp(r, "B")) {
guest_base = strtol(argv[optind++], NULL, 0);
have_guest_base = 1;
+ } else if (!strcmp(r, "R")) {
+ char *p;
+ int shift = 0;
+ reserved_va = strtoul(argv[optind++], &p, 0);
+ switch (*p) {
+ case 'k':
+ case 'K':
+ shift = 10;
+ break;
+ case 'M':
+ shift = 20;
+ break;
+ case 'G':
+ shift = 30;
+ break;
+ }
+ if (shift) {
+ unsigned long unshifted = reserved_va;
+ p++;
+ reserved_va <<= shift;
+ if (((reserved_va >> shift) != unshifted)
+#if HOST_LONG_BITS > TARGET_VIRT_ADDR_SPACE_BITS
+ || (reserved_va > (1ul << TARGET_VIRT_ADDR_SPACE_BITS))
+#endif
+ ) {
+ fprintf(stderr, "Reserved virtual address too big\n");
+ exit(1);
+ }
+ }
+ if (*p) {
+ fprintf(stderr, "Unrecognised -R size suffix '%s'\n", p);
+ exit(1);
+ }
#endif
} else if (!strcmp(r, "drop-ld-preload")) {
(void) envlist_unsetenv(envlist, "LD_PRELOAD");
@@ -2893,6 +2928,34 @@ int main(int argc, char **argv, char **envp)
* proper page alignment for guest_base.
*/
guest_base = HOST_PAGE_ALIGN(guest_base);
+
+ if (reserved_va) {
+ void *p;
+ int flags;
+
+ flags = MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE;
+ if (have_guest_base) {
+ flags |= MAP_FIXED;
+ }
+ p = mmap((void *)guest_base, reserved_va, PROT_NONE, flags, -1, 0);
+ if (p == MAP_FAILED) {
+ fprintf(stderr, "Unable to reserve guest address space\n");
+ exit(1);
+ }
+ guest_base = (unsigned long)p;
+ /* Make sure the address is properly aligned. */
+ if (guest_base & ~qemu_host_page_mask) {
+ munmap(p, reserved_va);
+ p = mmap((void *)guest_base, reserved_va + qemu_host_page_size,
+ PROT_NONE, flags, -1, 0);
+ if (p == MAP_FAILED) {
+ fprintf(stderr, "Unable to reserve guest address space\n");
+ exit(1);
+ }
+ guest_base = HOST_PAGE_ALIGN((unsigned long)p);
+ }
+ qemu_log("Reserved 0x%lx bytes of guest address space\n", reserved_va);
+ }
#endif /* CONFIG_USE_GUEST_BASE */
/*
diff --git a/linux-user/mmap.c b/linux-user/mmap.c
index fd315aaabb..39da6dfb40 100644
--- a/linux-user/mmap.c
+++ b/linux-user/mmap.c
@@ -216,6 +216,40 @@ static abi_ulong mmap_next_start = TASK_UNMAPPED_BASE;
unsigned long last_brk;
+/* Subroutine of mmap_find_vma, used when we have pre-allocated a chunk
+ of guest address space. */
+static abi_ulong mmap_find_vma_reserved(abi_ulong start, abi_ulong size)
+{
+ abi_ulong addr;
+ abi_ulong last_addr;
+ int prot;
+ int looped = 0;
+
+ if (size > reserved_va) {
+ return (abi_ulong)-1;
+ }
+
+ last_addr = start;
+ for (addr = start; last_addr + size != addr; addr += qemu_host_page_size) {
+ if (last_addr + size >= reserved_va
+ || (abi_ulong)(last_addr + size) < last_addr) {
+ if (looped) {
+ return (abi_ulong)-1;
+ }
+ last_addr = qemu_host_page_size;
+ addr = 0;
+ looped = 1;
+ continue;
+ }
+ prot = page_get_flags(addr);
+ if (prot) {
+ last_addr = addr + qemu_host_page_size;
+ }
+ }
+ mmap_next_start = addr;
+ return last_addr;
+}
+
/*
* Find and reserve a free memory area of size 'size'. The search
* starts at 'start'.
@@ -237,6 +271,10 @@ abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size)
size = HOST_PAGE_ALIGN(size);
+ if (reserved_va) {
+ return mmap_find_vma_reserved(start, size);
+ }
+
addr = start;
wrapped = repeat = 0;
prev = 0;
@@ -525,6 +563,47 @@ fail:
return -1;
}
+static void mmap_reserve(abi_ulong start, abi_ulong size)
+{
+ abi_ulong real_start;
+ abi_ulong real_end;
+ abi_ulong addr;
+ abi_ulong end;
+ int prot;
+
+ real_start = start & qemu_host_page_mask;
+ real_end = HOST_PAGE_ALIGN(start + size);
+ end = start + size;
+ if (start > real_start) {
+ /* handle host page containing start */
+ prot = 0;
+ for (addr = real_start; addr < start; addr += TARGET_PAGE_SIZE) {
+ prot |= page_get_flags(addr);
+ }
+ if (real_end == real_start + qemu_host_page_size) {
+ for (addr = end; addr < real_end; addr += TARGET_PAGE_SIZE) {
+ prot |= page_get_flags(addr);
+ }
+ end = real_end;
+ }
+ if (prot != 0)
+ real_start += qemu_host_page_size;
+ }
+ if (end < real_end) {
+ prot = 0;
+ for (addr = end; addr < real_end; addr += TARGET_PAGE_SIZE) {
+ prot |= page_get_flags(addr);
+ }
+ if (prot != 0)
+ real_end -= qemu_host_page_size;
+ }
+ if (real_start != real_end) {
+ mmap(g2h(real_start), real_end - real_start, PROT_NONE,
+ MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE,
+ -1, 0);
+ }
+}
+
int target_munmap(abi_ulong start, abi_ulong len)
{
abi_ulong end, real_start, real_end, addr;
@@ -572,7 +651,11 @@ int target_munmap(abi_ulong start, abi_ulong len)
ret = 0;
/* unmap what we can */
if (real_start < real_end) {
- ret = munmap(g2h(real_start), real_end - real_start);
+ if (reserved_va) {
+ mmap_reserve(real_start, real_end - real_start);
+ } else {
+ ret = munmap(g2h(real_start), real_end - real_start);
+ }
}
if (ret == 0)
@@ -590,12 +673,18 @@ abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size,
mmap_lock();
- if (flags & MREMAP_FIXED)
+ if (flags & MREMAP_FIXED) {
host_addr = (void *) syscall(__NR_mremap, g2h(old_addr),
old_size, new_size,
flags,
- new_addr);
- else if (flags & MREMAP_MAYMOVE) {
+ g2h(new_addr));
+
+ if (reserved_va && host_addr != MAP_FAILED) {
+ /* If new and old addresses overlap then the above mremap will
+ already have failed with EINVAL. */
+ mmap_reserve(old_addr, old_size);
+ }
+ } else if (flags & MREMAP_MAYMOVE) {
abi_ulong mmap_start;
mmap_start = mmap_find_vma(0, new_size);
@@ -603,13 +692,32 @@ abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size,
if (mmap_start == -1) {
errno = ENOMEM;
host_addr = MAP_FAILED;
- } else
+ } else {
host_addr = (void *) syscall(__NR_mremap, g2h(old_addr),
old_size, new_size,
flags | MREMAP_FIXED,
g2h(mmap_start));
+ mmap_reserve(old_addr, old_size);
+ }
} else {
- host_addr = mremap(g2h(old_addr), old_size, new_size, flags);
+ int prot = 0;
+ if (reserved_va && old_size < new_size) {
+ abi_ulong addr;
+ for (addr = old_addr + old_size;
+ addr < old_addr + new_size;
+ addr++) {
+ prot |= page_get_flags(addr);
+ }
+ }
+ if (prot == 0) {
+ host_addr = mremap(g2h(old_addr), old_size, new_size, flags);
+ if (host_addr != MAP_FAILED && reserved_va && old_size > new_size) {
+ mmap_reserve(old_addr + old_size, new_size - old_size);
+ }
+ } else {
+ errno = ENOMEM;
+ host_addr = MAP_FAILED;
+ }
/* Check if address fits target address space */
if ((unsigned long)host_addr + new_size > (abi_ulong)-1) {
/* Revert mremap() changes */
diff --git a/qemu-doc.texi b/qemu-doc.texi
index 6647b7b2e4..e2c8e56380 100644
--- a/qemu-doc.texi
+++ b/qemu-doc.texi
@@ -2124,7 +2124,7 @@ qemu-i386 /usr/local/qemu-i386/wine/bin/wine \
@subsection Command line options
@example
-usage: qemu-i386 [-h] [-d] [-L path] [-s size] [-cpu model] [-g port] [-B offset] program [arguments...]
+usage: qemu-i386 [-h] [-d] [-L path] [-s size] [-cpu model] [-g port] [-B offset] [-R size] program [arguments...]
@end example
@table @option
@@ -2140,6 +2140,9 @@ Select CPU model (-cpu ? for list and additional feature selection)
Offset guest address by the specified number of bytes. This is useful when
the address region rewuired by guest applications is reserved on the host.
Ths option is currently only supported on some hosts.
+@item -R size
+Pre-allocate a guest virtual address space of the given size (in bytes).
+"G", "M", and "k" suffixes may be used when specifying the size.
@end table
Debug options: