summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorneal <neal>2008-06-18 13:52:29 +0000
committerneal <neal>2008-06-18 13:52:29 +0000
commitf4be7013d5e0113f73d756a4b35b657f4cec9383 (patch)
tree8284daa8f9debd9f8323b068748998f46340dd2c
parent5cd77a92a8d3b4661c35735ff8a34642a45fcb94 (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/ChangeLog21
-rw-r--r--libhurd-mm/Makefile.am1
-rw-r--r--libhurd-mm/anonymous.c112
-rw-r--r--libhurd-mm/map.c11
-rw-r--r--libhurd-mm/map.h22
-rw-r--r--libhurd-mm/mmap.c27
-rw-r--r--libhurd-mm/mprotect.c164
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;
+}
+