X-Git-Url: https://repo.jachan.dev/qemu.git/blobdiff_plain/680c877af48cf5f2fb4586f34c13fd21ce33bfec..e4baa9f00b9ddf47ac2811eb58a3931434b848f7:/linux-user/mmap.c diff --git a/linux-user/mmap.c b/linux-user/mmap.c index fd315aaabb..4888f53139 100644 --- a/linux-user/mmap.c +++ b/linux-user/mmap.c @@ -16,24 +16,14 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "qemu/osdep.h" #include "qemu.h" #include "qemu-common.h" +#include "translate-all.h" //#define DEBUG_MMAP -#if defined(CONFIG_USE_NPTL) static pthread_mutex_t mmap_mutex = PTHREAD_MUTEX_INITIALIZER; static __thread int mmap_lock_count; @@ -51,6 +41,11 @@ void mmap_unlock(void) } } +bool have_mmap_lock(void) +{ + return mmap_lock_count > 0 ? true : false; +} + /* Grab lock to make sure things are in a consistent state after fork(). */ void mmap_fork_start(void) { @@ -66,16 +61,6 @@ void mmap_fork_end(int child) else pthread_mutex_unlock(&mmap_mutex); } -#else -/* We aren't threadsafe to start with, so no need to worry about locking. */ -void mmap_lock(void) -{ -} - -void mmap_unlock(void) -{ -} -#endif /* NOTE: all the constants are the HOST ones, but addresses are target. */ int target_mprotect(abi_ulong start, abi_ulong len, int prot) @@ -196,26 +181,72 @@ static int mmap_frag(abi_ulong real_start, if (prot_new != (prot1 | PROT_WRITE)) mprotect(host_start, qemu_host_page_size, prot_new); } else { - /* just update the protection */ if (prot_new != prot1) { mprotect(host_start, qemu_host_page_size, prot_new); } + if (prot_new & PROT_WRITE) { + memset(g2h(start), 0, end - start); + } } return 0; } #if HOST_LONG_BITS == 64 && TARGET_ABI_BITS == 64 # define TASK_UNMAPPED_BASE (1ul << 38) -#elif defined(__CYGWIN__) -/* Cygwin doesn't have a whole lot of address space. */ -# define TASK_UNMAPPED_BASE 0x18000000 #else # define TASK_UNMAPPED_BASE 0x40000000 #endif -static abi_ulong mmap_next_start = TASK_UNMAPPED_BASE; +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 end_addr; + int prot; + int looped = 0; + + if (size > reserved_va) { + return (abi_ulong)-1; + } + + size = HOST_PAGE_ALIGN(size); + end_addr = start + size; + if (end_addr > reserved_va) { + end_addr = reserved_va; + } + addr = end_addr - qemu_host_page_size; + + while (1) { + if (addr > end_addr) { + if (looped) { + return (abi_ulong)-1; + } + end_addr = reserved_va; + addr = end_addr - qemu_host_page_size; + looped = 1; + continue; + } + prot = page_get_flags(addr); + if (prot) { + end_addr = addr; + } + if (addr + size == end_addr) { + break; + } + addr -= qemu_host_page_size; + } + + if (start == mmap_next_start) { + mmap_next_start = addr; + } + + return addr; +} + /* * Find and reserve a free memory area of size 'size'. The search * starts at 'start'. @@ -237,6 +268,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; @@ -304,7 +339,7 @@ abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size) munmap(ptr, size); /* ENOMEM if we checked the whole of the target address space. */ - if (addr == -1ul) { + if (addr == (abi_ulong)-1) { return (abi_ulong)-1; } else if (addr == 0) { if (wrapped) { @@ -312,7 +347,7 @@ abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size) } wrapped = 1; /* Don't actually use 0 when wrapping, instead indicate - that we'd truely like an allocation in low memory. */ + that we'd truly like an allocation in low memory. */ addr = (mmap_min_addr > TARGET_PAGE_SIZE ? TARGET_PAGE_ALIGN(mmap_min_addr) : TARGET_PAGE_SIZE); @@ -327,7 +362,6 @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int prot, int flags, int fd, abi_ulong offset) { abi_ulong ret, end, real_start, real_end, retaddr, host_offset, host_len; - unsigned long host_start; mmap_lock(); #ifdef DEBUG_MMAP @@ -366,6 +400,19 @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int prot, if (len == 0) goto the_end; real_start = start & qemu_host_page_mask; + host_offset = offset & qemu_host_page_mask; + + /* If the user is asking for the kernel to find a location, do that + before we truncate the length for mapping files below. */ + if (!(flags & MAP_FIXED)) { + host_len = len + offset - host_offset; + host_len = HOST_PAGE_ALIGN(host_len); + start = mmap_find_vma(real_start, host_len); + if (start == (abi_ulong)-1) { + errno = ENOMEM; + goto fail; + } + } /* When mapping files into a memory area larger than the file, accesses to pages beyond the file size will cause a SIGBUS. @@ -379,9 +426,9 @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int prot, may need to truncate file maps at EOF and add extra anonymous pages up to the targets page boundary. */ - if ((qemu_real_host_page_size < TARGET_PAGE_SIZE) - && !(flags & MAP_ANONYMOUS)) { - struct stat sb; + if ((qemu_real_host_page_size < qemu_host_page_size) && + !(flags & MAP_ANONYMOUS)) { + struct stat sb; if (fstat (fd, &sb) == -1) goto fail; @@ -391,35 +438,33 @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int prot, /* If so, truncate the file map at eof aligned with the hosts real pagesize. Additional anonymous maps will be created beyond EOF. */ - len = (sb.st_size - offset); - len += qemu_real_host_page_size - 1; - len &= ~(qemu_real_host_page_size - 1); + len = REAL_HOST_PAGE_ALIGN(sb.st_size - offset); } } if (!(flags & MAP_FIXED)) { - abi_ulong mmap_start; + unsigned long host_start; void *p; - host_offset = offset & qemu_host_page_mask; + host_len = len + offset - host_offset; host_len = HOST_PAGE_ALIGN(host_len); - mmap_start = mmap_find_vma(real_start, host_len); - if (mmap_start == (abi_ulong)-1) { - errno = ENOMEM; - goto fail; - } + /* Note: we prefer to control the mapping address. It is especially important if qemu_host_page_size > qemu_real_host_page_size */ - p = mmap(g2h(mmap_start), - host_len, prot, flags | MAP_FIXED | MAP_ANONYMOUS, -1, 0); + p = mmap(g2h(start), host_len, prot, + flags | MAP_FIXED | MAP_ANONYMOUS, -1, 0); if (p == MAP_FAILED) goto fail; /* update start so that it points to the file position at 'offset' */ host_start = (unsigned long)p; if (!(flags & MAP_ANONYMOUS)) { - p = mmap(g2h(mmap_start), len, prot, + p = mmap(g2h(start), len, prot, flags | MAP_FIXED, fd, host_offset); + if (p == MAP_FAILED) { + munmap(g2h(start), host_len); + goto fail; + } host_start += offset - host_offset; } start = h2g(host_start); @@ -461,10 +506,7 @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int prot, goto fail; if (!(prot & PROT_WRITE)) { ret = target_mprotect(start, len, prot); - if (ret != 0) { - start = ret; - goto the_end; - } + assert(ret == 0); } goto the_end; } @@ -488,7 +530,7 @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int prot, /* handle the end of the mapping */ if (end < real_end) { ret = mmap_frag(real_end - qemu_host_page_size, - real_end - qemu_host_page_size, real_end, + real_end - qemu_host_page_size, end, prot, flags, fd, offset + real_end - qemu_host_page_size - start); if (ret == -1) @@ -518,6 +560,7 @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int prot, page_dump(stdout); printf("\n"); #endif + tb_invalidate_phys_range(start, start + len); mmap_unlock(); return start; fail: @@ -525,6 +568,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,11 +656,17 @@ 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) + if (ret == 0) { page_set_flags(start, start + len, 0); + tb_invalidate_phys_range(start, start + len); + } mmap_unlock(); return ret; } @@ -590,12 +680,16 @@ abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size, mmap_lock(); - 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) { + if (flags & MREMAP_FIXED) { + host_addr = mremap(g2h(old_addr), old_size, new_size, + flags, 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 +697,32 @@ abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size, if (mmap_start == -1) { errno = ENOMEM; host_addr = MAP_FAILED; - } else - host_addr = (void *) syscall(__NR_mremap, g2h(old_addr), - old_size, new_size, - flags | MREMAP_FIXED, - g2h(mmap_start)); + } else { + host_addr = mremap(g2h(old_addr), old_size, new_size, + flags | MREMAP_FIXED, g2h(mmap_start)); + if (reserved_va) { + 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 */ @@ -627,6 +740,7 @@ abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size, page_set_flags(old_addr, old_addr + old_size, 0); page_set_flags(new_addr, new_addr + new_size, prot | PAGE_VALID); } + tb_invalidate_phys_range(new_addr, new_addr + new_size); mmap_unlock(); return new_addr; }