diff options
author | neal <neal> | 2008-06-18 13:52:29 +0000 |
---|---|---|
committer | neal <neal> | 2008-06-18 13:52:29 +0000 |
commit | f4be7013d5e0113f73d756a4b35b657f4cec9383 (patch) | |
tree | 8284daa8f9debd9f8323b068748998f46340dd2c | |
parent | 5cd77a92a8d3b4661c35735ff8a34642a45fcb94 (diff) |
2008-06-18 Neal H. Walfield <neal@gnu.org>
* mmap.c (mmap): If MAP_FIXED is specified, first unmap the
region.
(mmap): Don't panic if we fail to create a pager, just return
MAP_FAILED.
* map.h (map_destroy_t): New define.
(struct map): Add field destroy.
(map_create): Take additional argument destroy.
* map.c (map_create): Take additional argument destroy. Set
MAP->DESTROY to it.
(map_destroy): Call MAP->DESTROY. If MAP->PAGER->NO_REFS is NULL,
unlock MAP->PAGER->LOCK.
* anonymous.c (offset_compare): Support comparing ranges.
(mdestroy): New function.
(destroy): Don't free ANON->MAP_AREA.
(anonymous_pager_alloc): Pass mdestroy to map_create.
* mprotect.c: New file.
* Makefile.am (libhurd_mm_a_SOURCES): Add mprotect.c.
-rw-r--r-- | libhurd-mm/ChangeLog | 21 | ||||
-rw-r--r-- | libhurd-mm/Makefile.am | 1 | ||||
-rw-r--r-- | libhurd-mm/anonymous.c | 112 | ||||
-rw-r--r-- | libhurd-mm/map.c | 11 | ||||
-rw-r--r-- | libhurd-mm/map.h | 22 | ||||
-rw-r--r-- | libhurd-mm/mmap.c | 27 | ||||
-rw-r--r-- | libhurd-mm/mprotect.c | 164 |
7 files changed, 334 insertions, 24 deletions
diff --git a/libhurd-mm/ChangeLog b/libhurd-mm/ChangeLog index 8fb8ecd..9f9607a 100644 --- a/libhurd-mm/ChangeLog +++ b/libhurd-mm/ChangeLog @@ -1,3 +1,24 @@ +2008-06-18 Neal H. Walfield <neal@gnu.org> + + * mmap.c (mmap): If MAP_FIXED is specified, first unmap the + region. + (mmap): Don't panic if we fail to create a pager, just return + MAP_FAILED. + + * map.h (map_destroy_t): New define. + (struct map): Add field destroy. + (map_create): Take additional argument destroy. + * map.c (map_create): Take additional argument destroy. Set + MAP->DESTROY to it. + (map_destroy): Call MAP->DESTROY. If MAP->PAGER->NO_REFS is NULL, + unlock MAP->PAGER->LOCK. + * anonymous.c (offset_compare): Support comparing ranges. + (mdestroy): New function. + (destroy): Don't free ANON->MAP_AREA. + (anonymous_pager_alloc): Pass mdestroy to map_create. + * mprotect.c: New file. + * Makefile.am (libhurd_mm_a_SOURCES): Add mprotect.c. + 2008-06-17 Neal H. Walfield <neal@gnu.org> * pager.c (pager_init): Clear PAGER->LOCK. diff --git a/libhurd-mm/Makefile.am b/libhurd-mm/Makefile.am index 88e5b78..c45e422 100644 --- a/libhurd-mm/Makefile.am +++ b/libhurd-mm/Makefile.am @@ -47,6 +47,7 @@ libhurd_mm_a_SOURCES = mm.h \ pager.h pager.c \ anonymous.h anonymous.c \ mmap.c sbrk.c \ + mprotect.c \ mm-init.c \ $(ARCH_SOURCES) diff --git a/libhurd-mm/anonymous.c b/libhurd-mm/anonymous.c index d7cdc47..fc23db7 100644 --- a/libhurd-mm/anonymous.c +++ b/libhurd-mm/anonymous.c @@ -40,7 +40,7 @@ struct storage_desc { hurd_btree_node_t node; - /* Offset from start of mapping. */ + /* Offset from start of pager. */ uintptr_t offset; /* The allocated storage. */ addr_t storage; @@ -49,6 +49,24 @@ struct storage_desc static int offset_compare (const uintptr_t *a, const uintptr_t *b) { + bool have_range = (*a & 1) || (*b & 1); + if (unlikely (have_range)) + /* If the least significant bit is set, then we the following word + is the length. In this case, we are interested in overlap. */ + { + uintptr_t a_start = *a & ~1; + uintptr_t a_end = a_start + ((*a & 1) ? a[1] : 0); + uintptr_t b_start = *b & ~1; + uintptr_t b_end = b_start + ((*b & 1) ? b[1] : 0); + + if (a_end < b_start) + return -1; + if (a_start > b_end) + return 1; + /* Overlap. */ + return 0; + } + if (*a < *b) return -1; return *a != *b; @@ -295,6 +313,79 @@ fault (struct pager *pager, uintptr_t offset, int count, bool read_only, } static void +mdestroy (struct map *map) +{ + struct anonymous_pager *anon = (struct anonymous_pager *) map->pager; + +#ifndef NDEBUG + /* Void the area. */ + addr_t addr; + for (addr = ADDR (map->region.start, ADDR_BITS - PAGESIZE_LOG2); + addr_prefix (addr) < map->region.start + map->region.length; + addr = addr_add (addr, 1)) + { + /* This may fail if the page has not yet been faulted in. */ + as_slot_lookup_use + (addr, + ({ + error_t err; + err = rm_cap_rubout (meta_data_activity, ADDR_VOID, addr); + assert (! err); + slot->type = cap_void; + })); + } +#endif + + /* XXX: We assume that every byte is mapped by at most one mapping. + We may have to reexamine this assumption if we allow multiple + mappings onto the same part of a pager (e.g., via mremap). */ + + /* Free the storage in this region. */ + + uintptr_t offset[2]; + offset[0] = map->offset | 1; + offset[1] = map->region.length; + + hurd_btree_storage_desc_t *storage_descs; + storage_descs = (hurd_btree_storage_desc_t *) &anon->storage; + + struct storage_desc *next + = hurd_btree_storage_desc_find (storage_descs, &offset[0]); + if (next) + { + /* We destory STORAGE_DESC. Grab its pervious pointer + first. */ + struct storage_desc *prev = hurd_btree_storage_desc_prev (next); + + int dir; + struct storage_desc *storage_desc; + for (dir = 0; dir < 2; dir ++, next = prev) + while ((storage_desc = next)) + { + next = (dir == 0 ? hurd_btree_storage_desc_next (storage_desc) + : hurd_btree_storage_desc_prev (storage_desc)); + + if (storage_desc->offset < map->region.start + || (storage_desc->offset + > map->region.start + map->region.length - 1)) + break; + + storage_free (storage_desc->storage, false); + +#ifndef NDEBUG + /* When reallocating, we expect that the node field is 0. + libhurd-btree asserts this, so make it so. */ + memset (storage_desc, 0, sizeof (struct storage_desc)); +#endif + storage_desc_free (storage_desc); + } + } + + /* Free the map area. Should we also free the staging area? */ + as_free (PTR_TO_ADDR (map->region.start), map->region.length); +} + +static void destroy (struct pager *pager) { assert (! ss_mutex_trylock (&pager->lock)); @@ -302,9 +393,6 @@ destroy (struct pager *pager) struct anonymous_pager *anon = (struct anonymous_pager *) pager; - /* Free the map area. */ - as_free (anon->map_area, anon->map_area_count); - if (anon->staging_area) /* Free the staging area. */ { @@ -428,7 +516,13 @@ anonymous_pager_alloc (addr_t activity, /* No room for this region. */ { if ((flags & ANONYMOUS_FIXED)) - goto error_with_buffer; + { + debug (0, "(%x, %x (%x)): Specified range " ADDR_FMT "+%d " + "in use and ANONYMOUS_FIXED specified", + hint, length, hint + length - 1, + ADDR_PRINTF (anon->map_area), count); + goto error_with_buffer; + } } else { @@ -440,7 +534,11 @@ anonymous_pager_alloc (addr_t activity, { anon->map_area = as_alloc (width, count, true); if (ADDR_IS_VOID (anon->map_area)) - goto error_with_buffer; + { + debug (0, "(%x, %x (%x)): No VA available", + hint, length, hint + length - 1); + goto error_with_buffer; + } *addr_out = ADDR_TO_PTR (addr_extend (anon->map_area, 0, width)); } @@ -462,7 +560,7 @@ anonymous_pager_alloc (addr_t activity, /* Install the map. */ struct region region = { (uintptr_t) *addr_out, length }; - struct map *map = map_create (region, access, &anon->pager, 0); + struct map *map = map_create (region, access, &anon->pager, 0, mdestroy); /* There is no way that we get a region conflict. */ if (! map) panic ("Memory exhausted."); diff --git a/libhurd-mm/map.c b/libhurd-mm/map.c index 08d55ae..6ae951a 100644 --- a/libhurd-mm/map.c +++ b/libhurd-mm/map.c @@ -154,7 +154,8 @@ map_install (struct map *map) struct map * map_create (struct region region, enum map_access access, - struct pager *pager, uintptr_t offset) + struct pager *pager, uintptr_t offset, + map_destroy_t destroy) { maps_lock_lock (); ss_mutex_lock (&pager->lock); @@ -165,6 +166,7 @@ map_create (struct region region, enum map_access access, map->pager = pager; map->offset = offset; map->access = access; + map->destroy = destroy; if (! map_install (map)) { @@ -199,16 +201,23 @@ map_destroy (struct map *map) /* Drop our reference. */ assert (map->pager->maps); + if (map->destroy) + map->destroy (map); + if (map->pager->maps == map && ! map->map_list_next) /* This is the last reference. */ { map->pager->maps = NULL; + if (map->pager->no_refs) map->pager->no_refs (map->pager); + else + ss_mutex_unlock (&map->pager->lock); } else { list_unlink (map); + ss_mutex_unlock (&map->pager->lock); } diff --git a/libhurd-mm/map.h b/libhurd-mm/map.h index 1e2628a..dba2389 100644 --- a/libhurd-mm/map.h +++ b/libhurd-mm/map.h @@ -68,6 +68,11 @@ region_compare (const struct region *a, const struct region *b) return 0; } +/* Forward. */ +struct pager; +struct map; + + enum map_access { MAP_ACCESS_NONE = 0, @@ -77,8 +82,8 @@ enum map_access MAP_ACCESS_ALL = MAP_ACCESS_READ | MAP_ACCESS_WRITE, }; -/* Forward. */ -struct pager; +/* Call-back invoked when destroying a map. */ +typedef void (*map_destroy_t) (struct map *map); struct map { @@ -109,6 +114,9 @@ struct map /* Each map is attached to its pager's list of maps. */ struct map *map_list_next; struct map **map_list_prevp; + + + map_destroy_t destroy; }; #define MAP_FMT REGION_FMT " @ %p+%x (%s%s)" @@ -167,11 +175,13 @@ maps_lock_unlock (void) } /* Map the region REGION with access described by ACCESS to the pager - PAGER starting at offset OFFSET. Maps may not overlap. Returns - true on success, false otherwise. This function takes and releases - MAPS_LOCK and PAGER->LOCK. */ + PAGER starting at offset OFFSET. DESTROY is a callback called just + before the map is fully destroyed. It may be NULL. Maps may not + overlap. Returns true on success, false otherwise. This function + takes and releases MAPS_LOCK and PAGER->LOCK. */ extern struct map *map_create (struct region region, enum map_access access, - struct pager *pager, uintptr_t offset); + struct pager *pager, uintptr_t offset, + map_destroy_t destroy); /* Disconnect the map MAP from MAPS. MAP will no longer resolve faults, however, any previously mapped pages may remain accessible. diff --git a/libhurd-mm/mmap.c b/libhurd-mm/mmap.c index 3c03295..1306053 100644 --- a/libhurd-mm/mmap.c +++ b/libhurd-mm/mmap.c @@ -4,20 +4,20 @@ This file is part of the GNU Hurd. - The GNU Hurd is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. + GNU Hurd is free software: you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. - The GNU Hurd is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of + GNU Hurd 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public - License along with the GNU C Library; if not, write to the Free - Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - 02111-1307 USA. */ + License along with GNU Hurd. If not, see + <http://www.gnu.org/licenses/>. */ + #include <hurd/stddef.h> #include <hurd/addr.h> @@ -59,6 +59,10 @@ mmap (void *addr, size_t length, int protect, int flags, addr); return MAP_FAILED; } + + /* Unmap any existing mapping. */ + if (munmap (addr, length) == -1) + return MAP_FAILED; } else { @@ -78,7 +82,10 @@ mmap (void *addr, size_t length, int protect, int flags, (flags & MAP_FIXED) ? ANONYMOUS_FIXED: 0, NULL, &addr); if (! pager) - panic ("Failed to create pager!"); + { + debug (0, "Failed to create pager!"); + return MAP_FAILED; + } debug (5, "Allocated memory %x-%x", addr, addr + length); diff --git a/libhurd-mm/mprotect.c b/libhurd-mm/mprotect.c new file mode 100644 index 0000000..722f481 --- /dev/null +++ b/libhurd-mm/mprotect.c @@ -0,0 +1,164 @@ +/* mprotect.c - mprotect implementation. + Copyright (C) 2008 Free Software Foundation, Inc. + Written by Neal H. Walfield <neal@gnu.org>. + + This file is part of the GNU Hurd. + + GNU Hurd is free software: you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + GNU Hurd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with GNU Hurd. If not, see + <http://www.gnu.org/licenses/>. */ + +#include <hurd/stddef.h> +#include <hurd/addr.h> +#include <hurd/as.h> +#include <hurd/storage.h> +#include <hurd/anonymous.h> +#include <hurd/map.h> + +#include <sys/mman.h> +#include <stdint.h> + +int +mprotect (void *addr, size_t length, int prot) +{ + uintptr_t start = (uintptr_t) addr; + uintptr_t end = start + length - 1; + + debug (0, "(%p, %x (%p))", addr, length, end); + + struct region region = { (uintptr_t) addr, length }; + + enum map_access access = 0; + if ((prot & PROT_READ)) + access = MAP_ACCESS_READ; + if ((prot & PROT_WRITE)) + access |= MAP_ACCESS_WRITE; + + maps_lock_lock (); + + /* Find any pager that overlaps within the designated region. */ + struct map *map = map_find (region); + if (! map) + /* There are none. We're done. */ + { + maps_lock_unlock (); + return 0; + } + + /* There may be pagers that come lexically before as well as after + PAGER. We start with PAGER and scan forward and then do the same + but scan backwards. */ + struct map *prev = hurd_btree_map_prev (map); + + int dir; + for (dir = 0; dir < 2; dir ++, map = prev) + for (; + map; + map = (dir == 0 ? hurd_btree_map_next (map) + : hurd_btree_map_prev (map))) + { + uintptr_t map_start = map->region.start; + uintptr_t map_end = map_start + map->region.length - 1; + + debug (5, "(%x-%x): considering %x-%x", + start, end, map_start, map_end); + + if (map_start > end || map_end < start) + break; + + if (map->access == access) + /* The access is already correct. Nothing to do. */ + continue; + + if (map_start < start) + /* We need to split. */ + { + debug (5, "(%x-%x): splitting %x-%x at offset %x", + start, end, map_start, map_end, start - map_start); + + struct map *second = map_split (map, start - map_start); + if (second) + map = second; + else + { + panic ("munmap (%x-%x) but cannot split map at %x-%x", + start, end, map_start, map_end); + } + + map_start = start; + } + + if (map_end > end) + /* We need to split. */ + { + debug (5, "(%x-%x): splitting %x-%x at offset %x", + start, end, map_start, map_end, end - map_start + 1); + + struct map *second = map_split (map, end - map_start + 1); + if (! second) + { + panic ("munmap (%x-%x) but cannot split map at %x-%x", + start, end, map_start, map_end); + } + + map_end = end; + } + + if (map->access == MAP_ACCESS_WRITE + || (map->access == MAP_ACCESS_READ && access == 0)) + /* We need to reduce the permission on all capabilities in + the area. */ + { + map->access = access; + + addr_t addr; + for (addr = ADDR (map_start, ADDR_BITS - PAGESIZE_LOG2); + addr_prefix (addr) < map_end; + addr = addr_add (addr, 1)) + { + /* This may fail if the page has not yet been faulted + in. That's okay: it will get the right + permissions. */ + as_slot_lookup_use + (addr, + ({ + if (map->access == 0) + { + error_t err; + err = rm_cap_rubout (meta_data_activity, + ADDR_VOID, addr); + assert (! err); + slot->type = cap_void; + } + else + { + bool ret; + ret = cap_copy_x (meta_data_activity, + ADDR_VOID, slot, addr, + ADDR_VOID, *slot, addr, + CAP_COPY_WEAKEN, + CAP_PROPERTIES_VOID); + assert (ret); + } + })); + } + } + else + map->access = access; + } + + maps_lock_unlock (); + + return 0; +} + |