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:
parent
43cac7405e
commit
3b8a1e5d8c
|
@ -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--;
|
||||
|
||||
/*
|
||||
|
|
Loading…
Reference in New Issue