Implement PT page stealing for extreme low memory conditions. This

has the side-effect of eliminating a locking protocol error in some
memory-desperation code in pmap_physpage_alloc().
This commit is contained in:
thorpej 1999-11-02 18:09:31 +00:00
parent 43cac7405e
commit 3b8a1e5d8c
1 changed files with 248 additions and 91 deletions

View File

@ -1,4 +1,4 @@
/* $NetBSD: pmap.c,v 1.113 1999/11/01 20:25:39 thorpej Exp $ */
/* $NetBSD: pmap.c,v 1.114 1999/11/02 18:09:31 thorpej Exp $ */
/*-
* Copyright (c) 1998, 1999 The NetBSD Foundation, Inc.
@ -154,7 +154,7 @@
#include <sys/cdefs.h> /* RCS ID & Copyright macro defns */
__KERNEL_RCSID(0, "$NetBSD: pmap.c,v 1.113 1999/11/01 20:25:39 thorpej Exp $");
__KERNEL_RCSID(0, "$NetBSD: pmap.c,v 1.114 1999/11/02 18:09:31 thorpej Exp $");
#include <sys/param.h>
#include <sys/systm.h>
@ -443,7 +443,7 @@ struct pmap_tlb_shootdown_q {
struct simplelock pq_slock; /* spin lock on queue */
} pmap_tlb_shootdown_q[ALPHA_MAXPROCS];
/* If we have more pending jobs than this, we just nail the while TLB. */
/* If we have more pending jobs than this, we just nail the whole TLB. */
#define PMAP_TLB_SHOOTDOWN_MAXJOBS 6
struct pool pmap_tlb_shootdown_job_pool;
@ -469,12 +469,25 @@ pa_to_pvh(pa)
return (&vm_physmem[bank].pmseg.pvhead[pg]);
}
/*
* Optional argument passed to pmap_remove_mapping() for stealing mapping
* resources.
*/
struct prm_thief {
int prmt_flags; /* flags; what to steal */
struct pv_entry *prmt_pv; /* the stolen PV entry */
pt_entry_t *prmt_ptp; /* the stolen PT page */
};
#define PRMT_PV 0x0001 /* steal the PV entry */
#define PRMT_PTP 0x0002 /* steal the PT page */
/*
* Internal routines
*/
void alpha_protection_init __P((void));
boolean_t pmap_remove_mapping __P((pmap_t, vaddr_t, pt_entry_t *,
boolean_t, long, struct pv_entry **));
boolean_t, long, struct prm_thief *));
void pmap_changebit __P((paddr_t, pt_entry_t, pt_entry_t, long));
/*
@ -483,9 +496,10 @@ void pmap_changebit __P((paddr_t, pt_entry_t, pt_entry_t, long));
void pmap_lev1map_create __P((pmap_t, long));
void pmap_lev1map_destroy __P((pmap_t, long));
void pmap_ptpage_alloc __P((pmap_t, pt_entry_t *, int));
void pmap_ptpage_free __P((pmap_t, pt_entry_t *));
void pmap_l3pt_delref __P((pmap_t, vaddr_t, pt_entry_t *, pt_entry_t *,
pt_entry_t *, long));
boolean_t pmap_ptpage_steal __P((pmap_t, int, paddr_t *));
void pmap_ptpage_free __P((pmap_t, pt_entry_t *, pt_entry_t **));
void pmap_l3pt_delref __P((pmap_t, vaddr_t, pt_entry_t *, long,
pt_entry_t **));
void pmap_l2pt_delref __P((pmap_t, pt_entry_t *, pt_entry_t *, long));
void pmap_l1pt_delref __P((pmap_t, pt_entry_t *, long));
@ -2478,19 +2492,21 @@ alpha_protection_init()
* be synchronized.
*/
boolean_t
pmap_remove_mapping(pmap, va, pte, dolock, cpu_id, pvp)
pmap_remove_mapping(pmap, va, pte, dolock, cpu_id, prmt)
pmap_t pmap;
vaddr_t va;
pt_entry_t *pte;
boolean_t dolock;
long cpu_id;
struct pv_entry **pvp;
struct prm_thief *prmt;
{
paddr_t pa;
boolean_t onpv;
boolean_t hadasm;
boolean_t isactive;
boolean_t needisync;
struct pv_entry **pvp;
pt_entry_t **ptp;
#ifdef DEBUG
if (pmapdebug & (PDB_FOLLOW|PDB_REMOVE|PDB_PROTECT))
@ -2498,6 +2514,20 @@ pmap_remove_mapping(pmap, va, pte, dolock, cpu_id, pvp)
pmap, va, pte, dolock, cpu_id, pvp);
#endif
if (prmt != NULL) {
if (prmt->prmt_flags & PRMT_PV)
pvp = &prmt->prmt_pv;
else
pvp = NULL;
if (prmt->prmt_flags & PRMT_PTP)
ptp = &prmt->prmt_ptp;
else
ptp = NULL;
} else {
pvp = NULL;
ptp = NULL;
}
/*
* PTE not provided, compute it from pmap and va.
*/
@ -2539,17 +2569,12 @@ pmap_remove_mapping(pmap, va, pte, dolock, cpu_id, pvp)
* can free page table pages.
*/
if (pmap != pmap_kernel()) {
pt_entry_t *l1pte, *l2pte;
l1pte = pmap_l1pte(pmap, va);
l2pte = pmap_l2pte(pmap, va, l1pte);
/*
* Delete the reference on the level 3 table. It will
* delete references on the level 2 and 1 tables as
* appropriate.
*/
pmap_l3pt_delref(pmap, va, l1pte, l2pte, pte, cpu_id);
pmap_l3pt_delref(pmap, va, pte, cpu_id, ptp);
}
/*
@ -2955,11 +2980,14 @@ pmap_pv_alloc()
pt_entry_t *pte;
pmap_t pvpmap;
u_long cpu_id;
struct prm_thief prmt;
pv = pool_get(&pmap_pv_pool, PR_NOWAIT);
if (pv != NULL)
return (pv);
prmt.prmt_flags = PRMT_PV;
/*
* We were unable to allocate one from the pool. Try to
* steal one from another mapping. At this point we know that:
@ -3013,13 +3041,13 @@ pmap_pv_alloc()
* remove it and grab the pv_entry.
*/
if (pmap_remove_mapping(pvpmap, pv->pv_va,
pte, FALSE, cpu_id, &pv))
pte, FALSE, cpu_id, &prmt))
alpha_pal_imb();
/* Unlock everything and return. */
simple_unlock(&pvpmap->pm_slock);
simple_unlock(&pvh->pvh_slock);
return (pv);
return (prmt.prmt_pv);
}
simple_unlock(&pvh->pvh_slock);
}
@ -3090,73 +3118,33 @@ pmap_physpage_alloc(usage, pap)
struct vm_page *pg;
struct pv_head *pvh;
paddr_t pa;
pmap_t pmap;
int try;
try = 0; /* try a few times, but give up eventually */
pg = uvm_pagealloc(NULL, 0, NULL, UVM_PGA_USERESERVE);
if (pg != NULL) {
pa = VM_PAGE_TO_PHYS(pg);
pmap_zero_page(pa);
do {
pg = uvm_pagealloc(NULL, 0, NULL, UVM_PGA_USERESERVE);
if (pg != NULL) {
pa = VM_PAGE_TO_PHYS(pg);
pmap_zero_page(pa);
pvh = pa_to_pvh(pa);
simple_lock(&pvh->pvh_slock);
pvh = pa_to_pvh(pa);
simple_lock(&pvh->pvh_slock);
#ifdef DIAGNOSTIC
if (pvh->pvh_usage != PGU_NORMAL) {
printf("pmap_physpage_alloc: page 0x%lx is "
"in use (%s)\n", pa,
pmap_pgu_strings[pvh->pvh_usage]);
goto die;
}
if (pvh->pvh_refcnt != 0) {
printf("pmap_physpage_alloc: page 0x%lx has "
"%d references\n", pa, pvh->pvh_refcnt);
goto die;
}
if (pvh->pvh_usage != PGU_NORMAL) {
printf("pmap_physpage_alloc: page 0x%lx is "
"in use (%s)\n", pa,
pmap_pgu_strings[pvh->pvh_usage]);
panic("pmap_physpage_alloc");
}
if (pvh->pvh_refcnt != 0) {
printf("pmap_physpage_alloc: page 0x%lx has "
"%d references\n", pa, pvh->pvh_refcnt);
panic("pmap_physpage_alloc");
}
#endif
pvh->pvh_usage = usage;
simple_unlock(&pvh->pvh_slock);
*pap = pa;
return (TRUE);
}
/*
* We are in an extreme low memory condition.
* Find any inactive pmap and forget all of
* the physical mappings (the upper-layer
* VM code will rebuild them the next time
* the pmap is activated).
*/
simple_lock(&pmap_all_pmaps_slock);
for (pmap = TAILQ_FIRST(&pmap_all_pmaps); pmap != NULL;
pmap = TAILQ_NEXT(pmap, pm_list)) {
/*
* Don't garbage-collect pmaps that reference
* kernel_lev1map. They don't have any user PT
* pages to free.
*/
if (pmap->pm_lev1map != kernel_lev1map &&
pmap->pm_cpus == 0) {
pmap_collect(pmap);
break;
}
}
simple_unlock(&pmap_all_pmaps_slock);
} while (try++ < 5);
/*
* If we couldn't get any more pages after 5 tries, just
* give up.
*/
printf("pmap_physpage_alloc: no pages for %s page available after "
"5 tries\n", pmap_pgu_strings[usage]);
pvh->pvh_usage = usage;
simple_unlock(&pvh->pvh_slock);
*pap = pa;
return (TRUE);
}
return (FALSE);
#ifdef DIAGNOSTIC
die:
panic("pmap_physpage_alloc");
#endif
}
/*
@ -3291,8 +3279,14 @@ pmap_lev1map_create(pmap, cpu_id)
/*
* Allocate a page for the level 1 table.
*/
if (pmap_physpage_alloc(PGU_L1PT, &ptpa) == FALSE)
panic("pmap_lev1map_create: no pages available");
if (pmap_physpage_alloc(PGU_L1PT, &ptpa) == FALSE) {
/*
* Yow! No free pages! Try to steal a PT page from
* another pmap!
*/
if (pmap_ptpage_steal(pmap, PGU_L1PT, &ptpa) == FALSE)
panic("pmap_lev1map_create: no pages available");
}
pmap->pm_lev1map = (pt_entry_t *) ALPHA_PHYS_TO_K0SEG(ptpa);
/*
@ -3392,8 +3386,14 @@ pmap_ptpage_alloc(pmap, pte, usage)
/*
* Allocate the page table page.
*/
if (pmap_physpage_alloc(usage, &ptpa) == FALSE)
panic("pmap_ptpage_alloc: no pages available");
if (pmap_physpage_alloc(usage, &ptpa) == FALSE) {
/*
* Yow! No free pages! Try to steal a PT page from
* another pmap!
*/
if (pmap_ptpage_steal(pmap, usage, &ptpa) == FALSE)
panic("pmap_ptpage_alloc: no pages available");
}
/*
* Initialize the referencing PTE.
@ -3412,9 +3412,10 @@ pmap_ptpage_alloc(pmap, pte, usage)
* Note: the pmap must already be locked.
*/
void
pmap_ptpage_free(pmap, pte)
pmap_ptpage_free(pmap, pte, ptp)
pmap_t pmap;
pt_entry_t *pte;
pt_entry_t **ptp;
{
paddr_t ptpa;
@ -3425,14 +3426,165 @@ pmap_ptpage_free(pmap, pte)
ptpa = pmap_pte_pa(pte);
*pte = PG_NV;
/*
* Check to see if we're stealing the PT page. If we are,
* zero it, and return the KSEG address of the page.
*/
if (ptp != NULL) {
pmap_zero_page(ptpa);
*ptp = (pt_entry_t *)ALPHA_PHYS_TO_K0SEG(ptpa);
} else {
#ifdef DEBUG
pmap_zero_page(ptpa);
pmap_zero_page(ptpa);
#endif
pmap_physpage_free(ptpa);
}
}
/*
* pmap_ptpage_steal:
*
* Steal a PT page from a pmap.
*/
boolean_t
pmap_ptpage_steal(pmap, usage, pap)
pmap_t pmap;
int usage;
paddr_t *pap;
{
struct pv_head *pvh;
pmap_t spmap;
int ps, l1idx, l2idx, l3idx;
pt_entry_t *lev2map, *lev3map;
vaddr_t va;
paddr_t pa;
struct prm_thief prmt;
u_long cpu_id = alpha_pal_whami();
boolean_t needisync = FALSE;
prmt.prmt_flags = PRMT_PTP;
prmt.prmt_ptp = NULL;
/*
* Free the page table page.
* We look for pmaps which do not reference kernel_lev1map (which
* would indicate that they are either the kernel pmap, or a user
* pmap with no valid mappings). Since the list of all pmaps is
* maintained in an LRU fashion, we should get a pmap that is
* `more inactive' than our current pmap (although this may not
* always be the case).
*
* We start looking for valid L1 PTEs at the lowest address,
* go to that L2, look for the first valid L2 PTE, and steal
* that L3 PT page.
*/
pmap_physpage_free(ptpa);
simple_lock(&pmap_all_pmaps_slock);
for (spmap = TAILQ_FIRST(&pmap_all_pmaps);
spmap != NULL; spmap = TAILQ_NEXT(spmap, pm_list)) {
/*
* Skip the kernel pmap and ourselves.
*/
if (spmap == pmap_kernel() || spmap == pmap)
continue;
PMAP_LOCK(spmap, ps);
if (spmap->pm_lev1map == kernel_lev1map) {
PMAP_UNLOCK(spmap, ps);
continue;
}
/*
* Have a candidate pmap. Loop through the PT pages looking
* for one we can steal.
*/
for (l1idx = 0; l1idx < NPTEPG; l1idx++) {
if (pmap_pte_v(&spmap->pm_lev1map[l1idx]) == 0)
continue;
lev2map = (pt_entry_t *)ALPHA_PHYS_TO_K0SEG(
pmap_pte_pa(&spmap->pm_lev1map[l1idx]));
for (l2idx = 0; l2idx < NPTEPG; l2idx++) {
if (pmap_pte_v(&lev2map[l2idx]) == 0)
continue;
lev3map = (pt_entry_t *)ALPHA_PHYS_TO_K0SEG(
pmap_pte_pa(&lev2map[l2idx]));
for (l3idx = 0; l3idx < NPTEPG; l3idx++) {
/*
* If the entry is valid and wired,
* we cannot steal this page.
*/
if (pmap_pte_v(&lev3map[l3idx]) &&
pmap_pte_w(&lev3map[l3idx]))
break;
}
/*
* If we scanned all of the current L3 table
* without finding a wired entry, we can
* steal this page!
*/
if (l3idx == NPTEPG)
goto found_one;
}
}
/*
* Didn't find something we could steal in this
* pmap, try the next one.
*/
PMAP_UNLOCK(spmap, ps);
continue;
found_one:
/* ...don't need this anymore. */
simple_unlock(&pmap_all_pmaps_slock);
/*
* Okay! We have a PT page we can steal. l1idx and
* l2idx indicate which L1 PTP and L2 PTP we should
* use to compute the virtual addresses the L3 PTP
* maps. Loop through all the L3 PTEs in this range
* and nuke the mappings for them. When we're through,
* we'll have a PT page pointed to by prmt.prmt_ptp!
*/
for (l3idx = 0,
va = (l1idx * ALPHA_L1SEG_SIZE) +
(l2idx * ALPHA_L2SEG_SIZE);
l3idx < NPTEPG && prmt.prmt_ptp == NULL;
l3idx++, va += PAGE_SIZE) {
if (pmap_pte_v(&lev3map[l3idx])) {
needisync |= pmap_remove_mapping(spmap, va,
&lev3map[l3idx], TRUE, cpu_id, &prmt);
}
}
PMAP_UNLOCK(spmap, ps);
if (needisync) {
alpha_pal_imb();
#if defined(MULTIPROCESSOR) && 0
alpha_broadcast_ipi(ALPHA_IPI_IMB);
#endif
}
#ifdef DIAGNOSTIC
if (prmt.prmt_ptp == NULL)
panic("pmap_ptptage_steal: failed");
if (prmt.prmt_ptp != lev3map)
panic("pmap_ptpage_steal: inconsistent");
#endif
pa = ALPHA_K0SEG_TO_PHYS((vaddr_t)prmt.prmt_ptp);
/*
* Don't bother locking here; the assignment is atomic.
*/
pvh = pa_to_pvh(pa);
pvh->pvh_usage = usage;
*pap = pa;
return (TRUE);
}
simple_unlock(&pmap_all_pmaps_slock);
return (FALSE);
}
/*
@ -3444,12 +3596,17 @@ pmap_ptpage_free(pmap, pte)
* Note: the pmap must already be locked.
*/
void
pmap_l3pt_delref(pmap, va, l1pte, l2pte, l3pte, cpu_id)
pmap_l3pt_delref(pmap, va, l3pte, cpu_id, ptp)
pmap_t pmap;
vaddr_t va;
pt_entry_t *l1pte, *l2pte, *l3pte;
pt_entry_t *l3pte;
long cpu_id;
pt_entry_t **ptp;
{
pt_entry_t *l1pte, *l2pte;
l1pte = pmap_l1pte(pmap, va);
l2pte = pmap_l2pte(pmap, va, l1pte);
#ifdef DIAGNOSTIC
if (pmap == pmap_kernel())
@ -3465,7 +3622,7 @@ pmap_l3pt_delref(pmap, va, l1pte, l2pte, l3pte, cpu_id)
printf("pmap_l3pt_delref: freeing level 3 table at "
"0x%lx\n", pmap_pte_pa(l2pte));
#endif
pmap_ptpage_free(pmap, l2pte);
pmap_ptpage_free(pmap, l2pte, ptp);
pmap->pm_nlev3--;
/*
@ -3522,7 +3679,7 @@ pmap_l2pt_delref(pmap, l1pte, l2pte, cpu_id)
printf("pmap_l2pt_delref: freeing level 2 table at "
"0x%lx\n", pmap_pte_pa(l1pte));
#endif
pmap_ptpage_free(pmap, l1pte);
pmap_ptpage_free(pmap, l1pte, NULL);
pmap->pm_nlev2--;
/*