summaryrefslogtreecommitdiffstats
path: root/sys/vm/vm_map.c
diff options
context:
space:
mode:
authorkib <kib@FreeBSD.org>2013-07-11 05:55:08 +0000
committerkib <kib@FreeBSD.org>2013-07-11 05:55:08 +0000
commit04554f0bf4d7f86323af1b2e5fff31973e050499 (patch)
treedd524061e748a1eb712e48db8db9d8b5e82ff1f4 /sys/vm/vm_map.c
parentda0e8446db5fe341cf068d68a7c012392cb2b064 (diff)
downloadFreeBSD-src-04554f0bf4d7f86323af1b2e5fff31973e050499.zip
FreeBSD-src-04554f0bf4d7f86323af1b2e5fff31973e050499.tar.gz
The mlockall() or VM_MAP_WIRE_HOLESOK does not interact properly with
parallel creation of the map entries, e.g. by mmap() or stack growing. It also breaks when other entry is wired in parallel. The vm_map_wire() iterates over the map entries in the region, and assumes that map entries it finds are marked as in transition before, also that any entry marked as in transition, are marked by the current invocation of vm_map_wire(). This is not true for new entries in the holes. Add the thread owner of the MAP_ENTRY_IN_TRANSITION flag to struct vm_map_entry. In vm_map_wire() and vm_map_unwire(), only process the entries which transition owner is the current thread. Reported and tested by: pho Reviewed by: alc Sponsored by: The FreeBSD Foundation MFC after: 2 weeks
Diffstat (limited to 'sys/vm/vm_map.c')
-rw-r--r--sys/vm/vm_map.c62
1 files changed, 51 insertions, 11 deletions
diff --git a/sys/vm/vm_map.c b/sys/vm/vm_map.c
index 1fee839..778c054 100644
--- a/sys/vm/vm_map.c
+++ b/sys/vm/vm_map.c
@@ -2281,6 +2281,7 @@ vm_map_unwire(vm_map_t map, vm_offset_t start, vm_offset_t end,
* above.)
*/
entry->eflags |= MAP_ENTRY_IN_TRANSITION;
+ entry->wiring_thread = curthread;
/*
* Check the map for holes in the specified region.
* If VM_MAP_WIRE_HOLESOK was specified, skip this check.
@@ -2313,8 +2314,24 @@ done:
else
KASSERT(result, ("vm_map_unwire: lookup failed"));
}
- entry = first_entry;
- while (entry != &map->header && entry->start < end) {
+ for (entry = first_entry; entry != &map->header && entry->start < end;
+ entry = entry->next) {
+ /*
+ * If VM_MAP_WIRE_HOLESOK was specified, an empty
+ * space in the unwired region could have been mapped
+ * while the map lock was dropped for draining
+ * MAP_ENTRY_IN_TRANSITION. Moreover, another thread
+ * could be simultaneously wiring this new mapping
+ * entry. Detect these cases and skip any entries
+ * marked as in transition by us.
+ */
+ if ((entry->eflags & MAP_ENTRY_IN_TRANSITION) == 0 ||
+ entry->wiring_thread != curthread) {
+ KASSERT((flags & VM_MAP_WIRE_HOLESOK) != 0,
+ ("vm_map_unwire: !HOLESOK and new/changed entry"));
+ continue;
+ }
+
if (rv == KERN_SUCCESS && (!user_unwire ||
(entry->eflags & MAP_ENTRY_USER_WIRED))) {
if (user_unwire)
@@ -2330,15 +2347,15 @@ done:
OBJ_FICTITIOUS) != 0);
}
}
- KASSERT(entry->eflags & MAP_ENTRY_IN_TRANSITION,
- ("vm_map_unwire: in-transition flag missing"));
+ KASSERT((entry->eflags & MAP_ENTRY_IN_TRANSITION) != 0,
+ ("vm_map_unwire: in-transition flag missing"));
entry->eflags &= ~MAP_ENTRY_IN_TRANSITION;
+ entry->wiring_thread = NULL;
if (entry->eflags & MAP_ENTRY_NEEDS_WAKEUP) {
entry->eflags &= ~MAP_ENTRY_NEEDS_WAKEUP;
need_wakeup = TRUE;
}
vm_map_simplify_entry(map, entry);
- entry = entry->next;
}
vm_map_unlock(map);
if (need_wakeup)
@@ -2432,6 +2449,7 @@ vm_map_wire(vm_map_t map, vm_offset_t start, vm_offset_t end,
* above.)
*/
entry->eflags |= MAP_ENTRY_IN_TRANSITION;
+ entry->wiring_thread = curthread;
if ((entry->protection & (VM_PROT_READ | VM_PROT_EXECUTE)) == 0
|| (entry->protection & prot) != prot) {
entry->eflags |= MAP_ENTRY_WIRE_SKIPPED;
@@ -2523,10 +2541,27 @@ done:
else
KASSERT(result, ("vm_map_wire: lookup failed"));
}
- entry = first_entry;
- while (entry != &map->header && entry->start < end) {
+ for (entry = first_entry; entry != &map->header && entry->start < end;
+ entry = entry->next) {
if ((entry->eflags & MAP_ENTRY_WIRE_SKIPPED) != 0)
goto next_entry_done;
+
+ /*
+ * If VM_MAP_WIRE_HOLESOK was specified, an empty
+ * space in the unwired region could have been mapped
+ * while the map lock was dropped for faulting in the
+ * pages or draining MAP_ENTRY_IN_TRANSITION.
+ * Moreover, another thread could be simultaneously
+ * wiring this new mapping entry. Detect these cases
+ * and skip any entries marked as in transition by us.
+ */
+ if ((entry->eflags & MAP_ENTRY_IN_TRANSITION) == 0 ||
+ entry->wiring_thread != curthread) {
+ KASSERT((flags & VM_MAP_WIRE_HOLESOK) != 0,
+ ("vm_map_wire: !HOLESOK and new/changed entry"));
+ continue;
+ }
+
if (rv == KERN_SUCCESS) {
if (user_wire)
entry->eflags |= MAP_ENTRY_USER_WIRED;
@@ -2551,15 +2586,18 @@ done:
}
}
next_entry_done:
- KASSERT(entry->eflags & MAP_ENTRY_IN_TRANSITION,
- ("vm_map_wire: in-transition flag missing"));
- entry->eflags &= ~(MAP_ENTRY_IN_TRANSITION|MAP_ENTRY_WIRE_SKIPPED);
+ KASSERT((entry->eflags & MAP_ENTRY_IN_TRANSITION) != 0,
+ ("vm_map_wire: in-transition flag missing %p", entry));
+ KASSERT(entry->wiring_thread == curthread,
+ ("vm_map_wire: alien wire %p", entry));
+ entry->eflags &= ~(MAP_ENTRY_IN_TRANSITION |
+ MAP_ENTRY_WIRE_SKIPPED);
+ entry->wiring_thread = NULL;
if (entry->eflags & MAP_ENTRY_NEEDS_WAKEUP) {
entry->eflags &= ~MAP_ENTRY_NEEDS_WAKEUP;
need_wakeup = TRUE;
}
vm_map_simplify_entry(map, entry);
- entry = entry->next;
}
vm_map_unlock(map);
if (need_wakeup)
@@ -3193,6 +3231,7 @@ vmspace_fork(struct vmspace *vm1, vm_ooffset_t *fork_charge)
*new_entry = *old_entry;
new_entry->eflags &= ~(MAP_ENTRY_USER_WIRED |
MAP_ENTRY_IN_TRANSITION);
+ new_entry->wiring_thread = NULL;
new_entry->wired_count = 0;
if (new_entry->eflags & MAP_ENTRY_VN_WRITECNT) {
vnode_pager_update_writecount(object,
@@ -3227,6 +3266,7 @@ vmspace_fork(struct vmspace *vm1, vm_ooffset_t *fork_charge)
*/
new_entry->eflags &= ~(MAP_ENTRY_USER_WIRED |
MAP_ENTRY_IN_TRANSITION | MAP_ENTRY_VN_WRITECNT);
+ new_entry->wiring_thread = NULL;
new_entry->wired_count = 0;
new_entry->object.vm_object = NULL;
new_entry->cred = NULL;
OpenPOWER on IntegriCloud