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
This commit is contained in:
Alan Cox 2014-07-26 18:10:18 +00:00
parent a9f31ae0a8
commit 0346250941
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=269134
5 changed files with 93 additions and 27 deletions

View File

@ -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 *);

View File

@ -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)
{

View File

@ -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 @@ vm_map_unwire(vm_map_t map, vm_offset_t start, vm_offset_t end,
(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 @@ vm_map_wire(vm_map_t map, vm_offset_t start, vm_offset_t end,
* 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;
}

View File

@ -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>

View File

@ -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_ */