summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoralc <alc@FreeBSD.org>2014-07-26 18:10:18 +0000
committeralc <alc@FreeBSD.org>2014-07-26 18:10:18 +0000
commit2b42c1ddc80350071be36e48696ca0d0ea741c90 (patch)
tree94065957f3a189af052a04d2ce26af9380510865
parentfa1cc9a3b4766ee0c36fff550860e4bf09dffd11 (diff)
downloadFreeBSD-src-2b42c1ddc80350071be36e48696ca0d0ea741c90.zip
FreeBSD-src-2b42c1ddc80350071be36e48696ca0d0ea741c90.tar.gz
When unwiring a region of an address space, do not assume that the
underlying physical pages are mapped by the pmap. If, for example, the application has performed an mprotect(..., PROT_NONE) on any part of the wired region, then those pages will no longer be mapped by the pmap. So, using the pmap to lookup the wired pages in order to unwire them doesn't always work, and when it doesn't work wired pages are leaked. To avoid the leak, introduce and use a new function vm_object_unwire() that locates the wired pages by traversing the object and its backing objects. At the same time, switch from using pmap_change_wiring() to the recently introduced function pmap_unwire() for unwiring the region's mappings. pmap_unwire() is faster, because it operates a range of virtual addresses rather than a single virtual page at a time. Moreover, by operating on a range, it is superpage friendly. It doesn't waste time performing unnecessary demotions. Reported by: markj Reviewed by: kib Tested by: pho, jmg (arm) Sponsored by: EMC / Isilon Storage Division
-rw-r--r--sys/vm/vm_extern.h1
-rw-r--r--sys/vm/vm_fault.c3
-rw-r--r--sys/vm/vm_map.c42
-rw-r--r--sys/vm/vm_object.c72
-rw-r--r--sys/vm/vm_object.h2
5 files changed, 93 insertions, 27 deletions
diff --git a/sys/vm/vm_extern.h b/sys/vm/vm_extern.h
index 39a1a21..af567e6 100644
--- a/sys/vm/vm_extern.h
+++ b/sys/vm/vm_extern.h
@@ -81,7 +81,6 @@ int vm_fault_hold(vm_map_t map, vm_offset_t vaddr, vm_prot_t fault_type,
int fault_flags, vm_page_t *m_hold);
int vm_fault_quick_hold_pages(vm_map_t map, vm_offset_t addr, vm_size_t len,
vm_prot_t prot, vm_page_t *ma, int max_count);
-void vm_fault_unwire(vm_map_t, vm_offset_t, vm_offset_t, boolean_t);
int vm_fault_wire(vm_map_t, vm_offset_t, vm_offset_t, boolean_t);
int vm_forkproc(struct thread *, struct proc *, struct thread *, struct vmspace *, int);
void vm_waitproc(struct proc *);
diff --git a/sys/vm/vm_fault.c b/sys/vm/vm_fault.c
index ecaafdb..172e2f3 100644
--- a/sys/vm/vm_fault.c
+++ b/sys/vm/vm_fault.c
@@ -106,6 +106,7 @@ __FBSDID("$FreeBSD$");
#define PFFOR 4
static int vm_fault_additional_pages(vm_page_t, int, int, vm_page_t *, int *);
+static void vm_fault_unwire(vm_map_t, vm_offset_t, vm_offset_t, boolean_t);
#define VM_FAULT_READ_BEHIND 8
#define VM_FAULT_READ_MAX (1 + VM_FAULT_READ_AHEAD_MAX)
@@ -1186,7 +1187,7 @@ vm_fault_wire(vm_map_t map, vm_offset_t start, vm_offset_t end,
*
* Unwire a range of virtual addresses in a map.
*/
-void
+static void
vm_fault_unwire(vm_map_t map, vm_offset_t start, vm_offset_t end,
boolean_t fictitious)
{
diff --git a/sys/vm/vm_map.c b/sys/vm/vm_map.c
index b5108e2..c9d8cfa 100644
--- a/sys/vm/vm_map.c
+++ b/sys/vm/vm_map.c
@@ -132,6 +132,7 @@ static void _vm_map_init(vm_map_t map, pmap_t pmap, vm_offset_t min,
vm_offset_t max);
static void vm_map_entry_deallocate(vm_map_entry_t entry, boolean_t system_map);
static void vm_map_entry_dispose(vm_map_t map, vm_map_entry_t entry);
+static void vm_map_entry_unwire(vm_map_t map, vm_map_entry_t entry);
#ifdef INVARIANTS
static void vm_map_zdtor(void *mem, int size, void *arg);
static void vmspace_zdtor(void *mem, int size, void *arg);
@@ -2393,16 +2394,10 @@ done:
(entry->eflags & MAP_ENTRY_USER_WIRED))) {
if (user_unwire)
entry->eflags &= ~MAP_ENTRY_USER_WIRED;
- entry->wired_count--;
- if (entry->wired_count == 0) {
- /*
- * Retain the map lock.
- */
- vm_fault_unwire(map, entry->start, entry->end,
- entry->object.vm_object != NULL &&
- (entry->object.vm_object->flags &
- OBJ_FICTITIOUS) != 0);
- }
+ if (entry->wired_count == 1)
+ vm_map_entry_unwire(map, entry);
+ else
+ entry->wired_count--;
}
KASSERT((entry->eflags & MAP_ENTRY_IN_TRANSITION) != 0,
("vm_map_unwire: in-transition flag missing %p", entry));
@@ -2635,19 +2630,12 @@ done:
* unnecessary.
*/
entry->wired_count = 0;
- } else {
- if (!user_wire ||
- (entry->eflags & MAP_ENTRY_USER_WIRED) == 0)
+ } else if (!user_wire ||
+ (entry->eflags & MAP_ENTRY_USER_WIRED) == 0) {
+ if (entry->wired_count == 1)
+ vm_map_entry_unwire(map, entry);
+ else
entry->wired_count--;
- if (entry->wired_count == 0) {
- /*
- * Retain the map lock.
- */
- vm_fault_unwire(map, entry->start, entry->end,
- entry->object.vm_object != NULL &&
- (entry->object.vm_object->flags &
- OBJ_FICTITIOUS) != 0);
- }
}
next_entry_done:
KASSERT((entry->eflags & MAP_ENTRY_IN_TRANSITION) != 0,
@@ -2783,9 +2771,13 @@ vm_map_sync(
static void
vm_map_entry_unwire(vm_map_t map, vm_map_entry_t entry)
{
- vm_fault_unwire(map, entry->start, entry->end,
- entry->object.vm_object != NULL &&
- (entry->object.vm_object->flags & OBJ_FICTITIOUS) != 0);
+
+ VM_MAP_ASSERT_LOCKED(map);
+ KASSERT(entry->wired_count > 0,
+ ("vm_map_entry_unwire: entry %p isn't wired", entry));
+ pmap_unwire(map->pmap, entry->start, entry->end);
+ vm_object_unwire(entry->object.vm_object, entry->offset, entry->end -
+ entry->start, PQ_ACTIVE);
entry->wired_count = 0;
}
diff --git a/sys/vm/vm_object.c b/sys/vm/vm_object.c
index 504ca77..c3af9d7 100644
--- a/sys/vm/vm_object.c
+++ b/sys/vm/vm_object.c
@@ -2202,6 +2202,78 @@ vm_object_set_writeable_dirty(vm_object_t object)
vm_object_set_flag(object, OBJ_MIGHTBEDIRTY);
}
+/*
+ * vm_object_unwire:
+ *
+ * For each page offset within the specified range of the given object,
+ * find the highest-level page in the shadow chain and unwire it. A page
+ * must exist at every page offset, and the highest-level page must be
+ * wired.
+ */
+void
+vm_object_unwire(vm_object_t object, vm_ooffset_t offset, vm_size_t length,
+ uint8_t queue)
+{
+ vm_object_t tobject;
+ vm_page_t m, tm;
+ vm_pindex_t end_pindex, pindex, tpindex;
+ int depth, locked_depth;
+
+ KASSERT((offset & PAGE_MASK) == 0,
+ ("vm_object_unwire: offset is not page aligned"));
+ KASSERT((length & PAGE_MASK) == 0,
+ ("vm_object_unwire: length is not a multiple of PAGE_SIZE"));
+ /* The wired count of a fictitious page never changes. */
+ if ((object->flags & OBJ_FICTITIOUS) != 0)
+ return;
+ pindex = OFF_TO_IDX(offset);
+ end_pindex = pindex + atop(length);
+ locked_depth = 1;
+ VM_OBJECT_RLOCK(object);
+ m = vm_page_find_least(object, pindex);
+ while (pindex < end_pindex) {
+ if (m == NULL || pindex < m->pindex) {
+ /*
+ * The first object in the shadow chain doesn't
+ * contain a page at the current index. Therefore,
+ * the page must exist in a backing object.
+ */
+ tobject = object;
+ tpindex = pindex;
+ depth = 0;
+ do {
+ tpindex +=
+ OFF_TO_IDX(tobject->backing_object_offset);
+ tobject = tobject->backing_object;
+ KASSERT(tobject != NULL,
+ ("vm_object_unwire: missing page"));
+ if ((tobject->flags & OBJ_FICTITIOUS) != 0)
+ goto next_page;
+ depth++;
+ if (depth == locked_depth) {
+ locked_depth++;
+ VM_OBJECT_RLOCK(tobject);
+ }
+ } while ((tm = vm_page_lookup(tobject, tpindex)) ==
+ NULL);
+ } else {
+ tm = m;
+ m = TAILQ_NEXT(m, listq);
+ }
+ vm_page_lock(tm);
+ vm_page_unwire(tm, queue);
+ vm_page_unlock(tm);
+next_page:
+ pindex++;
+ }
+ /* Release the accumulated object locks. */
+ for (depth = 0; depth < locked_depth; depth++) {
+ tobject = object->backing_object;
+ VM_OBJECT_RUNLOCK(object);
+ object = tobject;
+ }
+}
+
#include "opt_ddb.h"
#ifdef DDB
#include <sys/kernel.h>
diff --git a/sys/vm/vm_object.h b/sys/vm/vm_object.h
index 8381992..1f6c7c2 100644
--- a/sys/vm/vm_object.h
+++ b/sys/vm/vm_object.h
@@ -291,6 +291,8 @@ void vm_object_shadow (vm_object_t *, vm_ooffset_t *, vm_size_t);
void vm_object_split(vm_map_entry_t);
boolean_t vm_object_sync(vm_object_t, vm_ooffset_t, vm_size_t, boolean_t,
boolean_t);
+void vm_object_unwire(vm_object_t object, vm_ooffset_t offset,
+ vm_size_t length, uint8_t queue);
#endif /* _KERNEL */
#endif /* _VM_OBJECT_ */
OpenPOWER on IntegriCloud