Prevent the unexpected deallocation of a page table page while performing

pmap_copy().  This entails additional locking in pmap_copy() and the
addition of a "flags" parameter to the page table page allocator for
specifying whether it may sleep when memory is unavailable.  (Already,
pmap_copy() checks the availability of memory, aborting if it is scarce.
In theory, another CPU could, however, allocate memory between
pmap_copy()'s check and the call to the page table page allocator,
causing the current thread to release its locks and sleep.  This change
makes this scenario impossible.)

Reviewed by: tegge@
This commit is contained in:
Alan Cox 2004-09-29 19:20:40 +00:00
parent 30b490009b
commit 0a752e9843
4 changed files with 84 additions and 41 deletions

View File

@ -110,6 +110,7 @@ __FBSDID("$FreeBSD$");
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mman.h>
#include <sys/msgbuf.h>
#include <sys/mutex.h>
@ -212,9 +213,9 @@ static int pmap_remove_entry(struct pmap *pmap, vm_page_t m,
vm_offset_t va, pd_entry_t ptepde);
static void pmap_insert_entry(pmap_t pmap, vm_offset_t va, vm_page_t m);
static vm_page_t pmap_allocpte(pmap_t pmap, vm_offset_t va);
static vm_page_t pmap_allocpte(pmap_t pmap, vm_offset_t va, int flags);
static vm_page_t _pmap_allocpte(pmap_t pmap, vm_pindex_t ptepindex);
static vm_page_t _pmap_allocpte(pmap_t pmap, vm_pindex_t ptepindex, int flags);
static int _pmap_unwire_pte_hold(pmap_t pmap, vm_offset_t va, vm_page_t m);
static int pmap_unuse_pt(pmap_t, vm_offset_t, pd_entry_t);
static vm_offset_t pmap_kmem_choose(vm_offset_t addr);
@ -1101,22 +1102,26 @@ pmap_pinit(pmap)
* race conditions.
*/
static vm_page_t
_pmap_allocpte(pmap, ptepindex)
pmap_t pmap;
vm_pindex_t ptepindex;
_pmap_allocpte(pmap_t pmap, vm_pindex_t ptepindex, int flags)
{
vm_page_t m, pdppg, pdpg;
KASSERT((flags & (M_NOWAIT | M_WAITOK)) == M_NOWAIT ||
(flags & (M_NOWAIT | M_WAITOK)) == M_WAITOK,
("_pmap_allocpte: flags is neither M_NOWAIT nor M_WAITOK"));
/*
* Allocate a page table page.
*/
if ((m = vm_page_alloc(NULL, ptepindex, VM_ALLOC_NOOBJ |
VM_ALLOC_WIRED | VM_ALLOC_ZERO)) == NULL) {
PMAP_UNLOCK(pmap);
vm_page_unlock_queues();
VM_WAIT;
vm_page_lock_queues();
PMAP_LOCK(pmap);
if (flags & M_WAITOK) {
PMAP_UNLOCK(pmap);
vm_page_unlock_queues();
VM_WAIT;
vm_page_lock_queues();
PMAP_LOCK(pmap);
}
/*
* Indicate the need to retry. While waiting, the page table
@ -1156,7 +1161,8 @@ _pmap_allocpte(pmap, ptepindex)
pml4 = &pmap->pm_pml4[pml4index];
if ((*pml4 & PG_V) == 0) {
/* Have to allocate a new pdp, recurse */
if (_pmap_allocpte(pmap, NUPDE + NUPDPE + pml4index) == NULL) {
if (_pmap_allocpte(pmap, NUPDE + NUPDPE + pml4index,
flags) == NULL) {
--m->wire_count;
vm_page_free(m);
return (NULL);
@ -1187,7 +1193,8 @@ _pmap_allocpte(pmap, ptepindex)
pml4 = &pmap->pm_pml4[pml4index];
if ((*pml4 & PG_V) == 0) {
/* Have to allocate a new pd, recurse */
if (_pmap_allocpte(pmap, NUPDE + pdpindex) == NULL) {
if (_pmap_allocpte(pmap, NUPDE + pdpindex,
flags) == NULL) {
--m->wire_count;
vm_page_free(m);
return (NULL);
@ -1199,7 +1206,8 @@ _pmap_allocpte(pmap, ptepindex)
pdp = &pdp[pdpindex & ((1ul << NPDPEPGSHIFT) - 1)];
if ((*pdp & PG_V) == 0) {
/* Have to allocate a new pd, recurse */
if (_pmap_allocpte(pmap, NUPDE + pdpindex) == NULL) {
if (_pmap_allocpte(pmap, NUPDE + pdpindex,
flags) == NULL) {
--m->wire_count;
vm_page_free(m);
return (NULL);
@ -1221,12 +1229,16 @@ _pmap_allocpte(pmap, ptepindex)
}
static vm_page_t
pmap_allocpte(pmap_t pmap, vm_offset_t va)
pmap_allocpte(pmap_t pmap, vm_offset_t va, int flags)
{
vm_pindex_t ptepindex;
pd_entry_t *pd;
vm_page_t m;
KASSERT((flags & (M_NOWAIT | M_WAITOK)) == M_NOWAIT ||
(flags & (M_NOWAIT | M_WAITOK)) == M_WAITOK,
("pmap_allocpte: flags is neither M_NOWAIT nor M_WAITOK"));
/*
* Calculate pagetable page index
*/
@ -1259,8 +1271,8 @@ pmap_allocpte(pmap_t pmap, vm_offset_t va)
* Here if the pte page isn't mapped, or if it has been
* deallocated.
*/
m = _pmap_allocpte(pmap, ptepindex);
if (m == NULL)
m = _pmap_allocpte(pmap, ptepindex, flags);
if (m == NULL && (flags & M_WAITOK))
goto retry;
}
return (m);
@ -1848,7 +1860,7 @@ pmap_enter(pmap_t pmap, vm_offset_t va, vm_page_t m, vm_prot_t prot,
* resident, we are creating it here.
*/
if (va < VM_MAXUSER_ADDRESS) {
mpte = pmap_allocpte(pmap, va);
mpte = pmap_allocpte(pmap, va, M_WAITOK);
}
#if 0 && defined(PMAP_DIAGNOSTIC)
else {
@ -2029,7 +2041,8 @@ pmap_enter_quick(pmap_t pmap, vm_offset_t va, vm_page_t m, vm_page_t mpte)
mpte = PHYS_TO_VM_PAGE(*ptepa & PG_FRAME);
mpte->wire_count++;
} else {
mpte = _pmap_allocpte(pmap, ptepindex);
mpte = _pmap_allocpte(pmap, ptepindex,
M_WAITOK);
if (mpte == NULL)
goto retry;
}
@ -2224,7 +2237,13 @@ pmap_copy(pmap_t dst_pmap, pmap_t src_pmap, vm_offset_t dst_addr, vm_size_t len,
return;
vm_page_lock_queues();
PMAP_LOCK(dst_pmap);
if (dst_pmap < src_pmap) {
PMAP_LOCK(dst_pmap);
PMAP_LOCK(src_pmap);
} else {
PMAP_LOCK(src_pmap);
PMAP_LOCK(dst_pmap);
}
for (addr = src_addr; addr < end_addr; addr = va_next) {
pt_entry_t *src_pte, *dst_pte;
vm_page_t dstmpte, srcmpte;
@ -2303,9 +2322,12 @@ pmap_copy(pmap_t dst_pmap, pmap_t src_pmap, vm_offset_t dst_addr, vm_size_t len,
* pte still being around... allocpte can
* block.
*/
dstmpte = pmap_allocpte(dst_pmap, addr);
dstmpte = pmap_allocpte(dst_pmap, addr,
M_NOWAIT);
if (dstmpte == NULL)
break;
dst_pte = pmap_pte(dst_pmap, addr);
if ((*dst_pte == 0) && (ptetemp = *src_pte)) {
if (*dst_pte == 0) {
/*
* Clear the modified and
* accessed (referenced) bits
@ -2325,6 +2347,7 @@ pmap_copy(pmap_t dst_pmap, pmap_t src_pmap, vm_offset_t dst_addr, vm_size_t len,
}
}
vm_page_unlock_queues();
PMAP_UNLOCK(src_pmap);
PMAP_UNLOCK(dst_pmap);
}

View File

@ -240,7 +240,7 @@ extern struct pmap kernel_pmap_store;
mtx_assert(&(pmap)->pm_mtx, (type))
#define PMAP_LOCK_DESTROY(pmap) mtx_destroy(&(pmap)->pm_mtx)
#define PMAP_LOCK_INIT(pmap) mtx_init(&(pmap)->pm_mtx, "pmap", \
NULL, MTX_DEF)
NULL, MTX_DEF | MTX_DUPOK)
#define PMAP_LOCKED(pmap) mtx_owned(&(pmap)->pm_mtx)
#define PMAP_MTX(pmap) (&(pmap)->pm_mtx)
#define PMAP_TRYLOCK(pmap) mtx_trylock(&(pmap)->pm_mtx)

View File

@ -110,6 +110,7 @@ __FBSDID("$FreeBSD$");
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mman.h>
#include <sys/msgbuf.h>
#include <sys/mutex.h>
@ -256,9 +257,9 @@ static int pmap_remove_entry(struct pmap *pmap, vm_page_t m,
vm_offset_t va);
static void pmap_insert_entry(pmap_t pmap, vm_offset_t va, vm_page_t m);
static vm_page_t pmap_allocpte(pmap_t pmap, vm_offset_t va);
static vm_page_t pmap_allocpte(pmap_t pmap, vm_offset_t va, int flags);
static vm_page_t _pmap_allocpte(pmap_t pmap, unsigned ptepindex);
static vm_page_t _pmap_allocpte(pmap_t pmap, unsigned ptepindex, int flags);
static int _pmap_unwire_pte_hold(pmap_t pmap, vm_page_t m);
static pt_entry_t *pmap_pte_quick(pmap_t pmap, vm_offset_t va);
static void pmap_pte_release(pt_entry_t *pte);
@ -1161,23 +1162,27 @@ pmap_pinit(pmap)
* mapped correctly.
*/
static vm_page_t
_pmap_allocpte(pmap, ptepindex)
pmap_t pmap;
unsigned ptepindex;
_pmap_allocpte(pmap_t pmap, unsigned ptepindex, int flags)
{
vm_paddr_t ptepa;
vm_page_t m;
KASSERT((flags & (M_NOWAIT | M_WAITOK)) == M_NOWAIT ||
(flags & (M_NOWAIT | M_WAITOK)) == M_WAITOK,
("_pmap_allocpte: flags is neither M_NOWAIT nor M_WAITOK"));
/*
* Allocate a page table page.
*/
if ((m = vm_page_alloc(NULL, ptepindex, VM_ALLOC_NOOBJ |
VM_ALLOC_WIRED | VM_ALLOC_ZERO)) == NULL) {
PMAP_UNLOCK(pmap);
vm_page_unlock_queues();
VM_WAIT;
vm_page_lock_queues();
PMAP_LOCK(pmap);
if (flags & M_WAITOK) {
PMAP_UNLOCK(pmap);
vm_page_unlock_queues();
VM_WAIT;
vm_page_lock_queues();
PMAP_LOCK(pmap);
}
/*
* Indicate the need to retry. While waiting, the page table
@ -1203,12 +1208,16 @@ _pmap_allocpte(pmap, ptepindex)
}
static vm_page_t
pmap_allocpte(pmap_t pmap, vm_offset_t va)
pmap_allocpte(pmap_t pmap, vm_offset_t va, int flags)
{
unsigned ptepindex;
pd_entry_t ptepa;
vm_page_t m;
KASSERT((flags & (M_NOWAIT | M_WAITOK)) == M_NOWAIT ||
(flags & (M_NOWAIT | M_WAITOK)) == M_WAITOK,
("pmap_allocpte: flags is neither M_NOWAIT nor M_WAITOK"));
/*
* Calculate pagetable page index
*/
@ -1241,8 +1250,8 @@ pmap_allocpte(pmap_t pmap, vm_offset_t va)
* Here if the pte page isn't mapped, or if it has
* been deallocated.
*/
m = _pmap_allocpte(pmap, ptepindex);
if (m == NULL)
m = _pmap_allocpte(pmap, ptepindex, flags);
if (m == NULL && (flags & M_WAITOK))
goto retry;
}
return (m);
@ -1906,7 +1915,7 @@ pmap_enter(pmap_t pmap, vm_offset_t va, vm_page_t m, vm_prot_t prot,
* resident, we are creating it here.
*/
if (va < VM_MAXUSER_ADDRESS) {
mpte = pmap_allocpte(pmap, va);
mpte = pmap_allocpte(pmap, va, M_WAITOK);
}
#if 0 && defined(PMAP_DIAGNOSTIC)
else {
@ -2095,7 +2104,8 @@ pmap_enter_quick(pmap_t pmap, vm_offset_t va, vm_page_t m, vm_page_t mpte)
mpte = PHYS_TO_VM_PAGE(ptepa);
mpte->wire_count++;
} else {
mpte = _pmap_allocpte(pmap, ptepindex);
mpte = _pmap_allocpte(pmap, ptepindex,
M_WAITOK);
if (mpte == NULL)
goto retry;
}
@ -2297,7 +2307,13 @@ pmap_copy(pmap_t dst_pmap, pmap_t src_pmap, vm_offset_t dst_addr, vm_size_t len,
return;
vm_page_lock_queues();
PMAP_LOCK(dst_pmap);
if (dst_pmap < src_pmap) {
PMAP_LOCK(dst_pmap);
PMAP_LOCK(src_pmap);
} else {
PMAP_LOCK(src_pmap);
PMAP_LOCK(dst_pmap);
}
sched_pin();
for (addr = src_addr; addr < end_addr; addr = pdnxt) {
pt_entry_t *src_pte, *dst_pte;
@ -2353,9 +2369,12 @@ pmap_copy(pmap_t dst_pmap, pmap_t src_pmap, vm_offset_t dst_addr, vm_size_t len,
* pte still being around... allocpte can
* block.
*/
dstmpte = pmap_allocpte(dst_pmap, addr);
dstmpte = pmap_allocpte(dst_pmap, addr,
M_NOWAIT);
if (dstmpte == NULL)
break;
dst_pte = pmap_pte_quick(dst_pmap, addr);
if ((*dst_pte == 0) && (ptetemp = *src_pte)) {
if (*dst_pte == 0) {
/*
* Clear the modified and
* accessed (referenced) bits
@ -2376,6 +2395,7 @@ pmap_copy(pmap_t dst_pmap, pmap_t src_pmap, vm_offset_t dst_addr, vm_size_t len,
}
sched_unpin();
vm_page_unlock_queues();
PMAP_UNLOCK(src_pmap);
PMAP_UNLOCK(dst_pmap);
}

View File

@ -302,7 +302,7 @@ extern struct pmap kernel_pmap_store;
mtx_assert(&(pmap)->pm_mtx, (type))
#define PMAP_LOCK_DESTROY(pmap) mtx_destroy(&(pmap)->pm_mtx)
#define PMAP_LOCK_INIT(pmap) mtx_init(&(pmap)->pm_mtx, "pmap", \
NULL, MTX_DEF)
NULL, MTX_DEF | MTX_DUPOK)
#define PMAP_LOCKED(pmap) mtx_owned(&(pmap)->pm_mtx)
#define PMAP_MTX(pmap) (&(pmap)->pm_mtx)
#define PMAP_TRYLOCK(pmap) mtx_trylock(&(pmap)->pm_mtx)