#if defined(PDSIM)
#include "pdsim.h"
#else
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: uvm_pdpolicy_clock.c,v 1.42 2025/05/20 10:22:27 bouyer Exp $");
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <sys/atomic.h>
#include <uvm/uvm.h>
#include <uvm/uvm_pdpolicy.h>
#include <uvm/uvm_pdpolicy_impl.h>
#include <uvm/uvm_stat.h>
#endif
#if !defined(CLOCK_PDQ_SIZE)
#define CLOCK_PDQ_SIZE 128
#endif
#define PQ_INACTIVE 0x00000010
#define PQ_ACTIVE 0x00000020
#if !defined(CLOCK_INACTIVEPCT)
#define CLOCK_INACTIVEPCT 33
#endif
struct uvmpdpol_globalstate {
kmutex_t lock;
struct pglist s_activeq
__aligned(COHERENCY_UNIT);
struct pglist s_inactiveq;
int s_active;
int s_inactive;
int s_inactarg;
struct uvm_pctparam s_anonmin;
struct uvm_pctparam s_filemin;
struct uvm_pctparam s_execmin;
struct uvm_pctparam s_anonmax;
struct uvm_pctparam s_filemax;
struct uvm_pctparam s_execmax;
struct uvm_pctparam s_inactivepct;
};
struct uvmpdpol_scanstate {
bool ss_anonreact, ss_filereact, ss_execreact;
struct vm_page ss_marker;
};
static void uvmpdpol_pageactivate_locked(struct vm_page *);
static void uvmpdpol_pagedeactivate_locked(struct vm_page *);
static void uvmpdpol_pagedequeue_locked(struct vm_page *);
static bool uvmpdpol_pagerealize_locked(struct vm_page *);
static struct uvm_cpu *uvmpdpol_flush(void);
static struct uvmpdpol_globalstate pdpol_state __cacheline_aligned;
static struct uvmpdpol_scanstate pdpol_scanstate;
PDPOL_EVCNT_DEFINE(reactexec)
PDPOL_EVCNT_DEFINE(reactfile)
PDPOL_EVCNT_DEFINE(reactanon)
static void
clock_tune(void)
{
struct uvmpdpol_globalstate *s = &pdpol_state;
s->s_inactarg = UVM_PCTPARAM_APPLY(&s->s_inactivepct,
s->s_active + s->s_inactive);
if (s->s_inactarg <= uvmexp.freetarg) {
s->s_inactarg = uvmexp.freetarg + 1;
}
}
void
uvmpdpol_scaninit(void)
{
struct uvmpdpol_globalstate *s = &pdpol_state;
struct uvmpdpol_scanstate *ss = &pdpol_scanstate;
int t;
bool anonunder, fileunder, execunder;
bool anonover, fileover, execover;
bool anonreact, filereact, execreact;
int64_t freepg, anonpg, filepg, execpg;
freepg = uvm_availmem(false);
anonpg = cpu_count_get(CPU_COUNT_ANONCLEAN) +
cpu_count_get(CPU_COUNT_ANONDIRTY) +
cpu_count_get(CPU_COUNT_ANONUNKNOWN);
execpg = cpu_count_get(CPU_COUNT_EXECPAGES);
filepg = cpu_count_get(CPU_COUNT_FILECLEAN) +
cpu_count_get(CPU_COUNT_FILEDIRTY) +
cpu_count_get(CPU_COUNT_FILEUNKNOWN) -
execpg;
mutex_enter(&s->lock);
t = s->s_active + s->s_inactive + freepg;
anonunder = anonpg <= UVM_PCTPARAM_APPLY(&s->s_anonmin, t);
fileunder = filepg <= UVM_PCTPARAM_APPLY(&s->s_filemin, t);
execunder = execpg <= UVM_PCTPARAM_APPLY(&s->s_execmin, t);
anonover = anonpg > UVM_PCTPARAM_APPLY(&s->s_anonmax, t);
fileover = filepg > UVM_PCTPARAM_APPLY(&s->s_filemax, t);
execover = execpg > UVM_PCTPARAM_APPLY(&s->s_execmax, t);
anonreact = anonunder || (!anonover && (fileover || execover));
filereact = fileunder || (!fileover && (anonover || execover));
execreact = execunder || (!execover && (anonover || fileover));
if (filereact && execreact && (anonreact || uvm_swapisfull())) {
anonreact = filereact = execreact = false;
}
ss->ss_anonreact = anonreact;
ss->ss_filereact = filereact;
ss->ss_execreact = execreact;
memset(&ss->ss_marker, 0, sizeof(ss->ss_marker));
ss->ss_marker.flags = PG_MARKER;
TAILQ_INSERT_HEAD(&pdpol_state.s_inactiveq, &ss->ss_marker, pdqueue);
mutex_exit(&s->lock);
}
void
uvmpdpol_scanfini(void)
{
struct uvmpdpol_globalstate *s = &pdpol_state;
struct uvmpdpol_scanstate *ss = &pdpol_scanstate;
mutex_enter(&s->lock);
TAILQ_REMOVE(&pdpol_state.s_inactiveq, &ss->ss_marker, pdqueue);
mutex_exit(&s->lock);
}
struct vm_page *
uvmpdpol_selectvictim(krwlock_t **plock)
{
struct uvmpdpol_globalstate *s = &pdpol_state;
struct uvmpdpol_scanstate *ss = &pdpol_scanstate;
struct vm_page *pg;
krwlock_t *lock;
mutex_enter(&s->lock);
while ( 1) {
struct vm_anon *anon;
struct uvm_object *uobj;
pg = TAILQ_NEXT(&ss->ss_marker, pdqueue);
if (pg == NULL) {
break;
}
KASSERT((pg->flags & PG_MARKER) == 0);
uvmexp.pdscans++;
mutex_enter(&pg->interlock);
if (uvmpdpol_pagerealize_locked(pg)) {
mutex_exit(&pg->interlock);
continue;
}
TAILQ_REMOVE(&pdpol_state.s_inactiveq, &ss->ss_marker,
pdqueue);
TAILQ_INSERT_AFTER(&pdpol_state.s_inactiveq, pg,
&ss->ss_marker, pdqueue);
anon = pg->uanon;
uobj = pg->uobject;
if (uobj && UVM_OBJ_IS_VTEXT(uobj) && ss->ss_execreact) {
uvmpdpol_pageactivate_locked(pg);
mutex_exit(&pg->interlock);
PDPOL_EVCNT_INCR(reactexec);
continue;
}
if (uobj && UVM_OBJ_IS_VNODE(uobj) &&
!UVM_OBJ_IS_VTEXT(uobj) && ss->ss_filereact) {
uvmpdpol_pageactivate_locked(pg);
mutex_exit(&pg->interlock);
PDPOL_EVCNT_INCR(reactfile);
continue;
}
if ((anon || UVM_OBJ_IS_AOBJ(uobj)) && ss->ss_anonreact) {
uvmpdpol_pageactivate_locked(pg);
mutex_exit(&pg->interlock);
PDPOL_EVCNT_INCR(reactanon);
continue;
}
mutex_exit(&s->lock);
lock = uvmpd_trylockowner(pg);
mutex_enter(&s->lock);
if (lock == NULL) {
continue;
}
if (pmap_is_referenced(pg)) {
mutex_enter(&pg->interlock);
uvmpdpol_pageactivate_locked(pg);
mutex_exit(&pg->interlock);
uvmexp.pdreact++;
rw_exit(lock);
continue;
}
*plock = lock;
break;
}
mutex_exit(&s->lock);
return pg;
}
void
uvmpdpol_balancequeue(int swap_shortage)
{
struct uvmpdpol_globalstate *s = &pdpol_state;
int inactive_shortage;
struct vm_page *p, marker;
krwlock_t *lock;
memset(&marker, 0, sizeof(marker));
marker.flags = PG_MARKER;
mutex_enter(&s->lock);
TAILQ_INSERT_HEAD(&pdpol_state.s_activeq, &marker, pdqueue);
for (;;) {
inactive_shortage =
pdpol_state.s_inactarg - pdpol_state.s_inactive;
if (inactive_shortage <= 0 && swap_shortage <= 0) {
break;
}
p = TAILQ_NEXT(&marker, pdqueue);
if (p == NULL) {
break;
}
KASSERT((p->flags & PG_MARKER) == 0);
mutex_enter(&p->interlock);
if (uvmpdpol_pagerealize_locked(p)) {
mutex_exit(&p->interlock);
continue;
}
TAILQ_REMOVE(&pdpol_state.s_activeq, &marker, pdqueue);
TAILQ_INSERT_AFTER(&pdpol_state.s_activeq, p, &marker,
pdqueue);
mutex_exit(&s->lock);
lock = uvmpd_trylockowner(p);
mutex_enter(&s->lock);
if (lock == NULL) {
continue;
}
if (swap_shortage > 0 && (p->flags & PG_SWAPBACKED) != 0 &&
(p->flags & PG_BUSY) == 0) {
if (uvmpd_dropswap(p)) {
swap_shortage--;
}
}
if (inactive_shortage > 0) {
pmap_clear_reference(p);
mutex_enter(&p->interlock);
uvmpdpol_pagedeactivate_locked(p);
mutex_exit(&p->interlock);
uvmexp.pddeact++;
inactive_shortage--;
}
rw_exit(lock);
}
TAILQ_REMOVE(&pdpol_state.s_activeq, &marker, pdqueue);
mutex_exit(&s->lock);
}
static void
uvmpdpol_pagedeactivate_locked(struct vm_page *pg)
{
struct uvmpdpol_globalstate *s __diagused = &pdpol_state;
KASSERT(mutex_owned(&s->lock));
KASSERT(mutex_owned(&pg->interlock));
KASSERT((pg->pqflags & (PQ_INTENT_MASK | PQ_INTENT_SET)) !=
(PQ_INTENT_D | PQ_INTENT_SET));
if (pg->pqflags & PQ_ACTIVE) {
TAILQ_REMOVE(&pdpol_state.s_activeq, pg, pdqueue);
KASSERT(pdpol_state.s_active > 0);
pdpol_state.s_active--;
}
if ((pg->pqflags & PQ_INACTIVE) == 0) {
KASSERT(pg->wire_count == 0);
TAILQ_INSERT_TAIL(&pdpol_state.s_inactiveq, pg, pdqueue);
pdpol_state.s_inactive++;
}
pg->pqflags &= ~(PQ_ACTIVE | PQ_INTENT_SET);
pg->pqflags |= PQ_INACTIVE;
}
void
uvmpdpol_pagedeactivate(struct vm_page *pg)
{
KASSERT(uvm_page_owner_locked_p(pg, false));
KASSERT(mutex_owned(&pg->interlock));
pmap_clear_reference(pg);
uvmpdpol_set_intent(pg, PQ_INTENT_I);
}
static void
uvmpdpol_pageactivate_locked(struct vm_page *pg)
{
struct uvmpdpol_globalstate *s __diagused = &pdpol_state;
KASSERT(mutex_owned(&s->lock));
KASSERT(mutex_owned(&pg->interlock));
KASSERT((pg->pqflags & (PQ_INTENT_MASK | PQ_INTENT_SET)) !=
(PQ_INTENT_D | PQ_INTENT_SET));
uvmpdpol_pagedequeue_locked(pg);
TAILQ_INSERT_TAIL(&pdpol_state.s_activeq, pg, pdqueue);
pdpol_state.s_active++;
pg->pqflags &= ~(PQ_INACTIVE | PQ_INTENT_SET);
pg->pqflags |= PQ_ACTIVE;
}
void
uvmpdpol_pageactivate(struct vm_page *pg)
{
KASSERT(uvm_page_owner_locked_p(pg, false));
KASSERT(mutex_owned(&pg->interlock));
uvmpdpol_set_intent(pg, PQ_INTENT_A);
}
static void
uvmpdpol_pagedequeue_locked(struct vm_page *pg)
{
struct uvmpdpol_globalstate *s __diagused = &pdpol_state;
KASSERT(mutex_owned(&s->lock));
KASSERT(mutex_owned(&pg->interlock));
if (pg->pqflags & PQ_ACTIVE) {
TAILQ_REMOVE(&pdpol_state.s_activeq, pg, pdqueue);
KASSERT((pg->pqflags & PQ_INACTIVE) == 0);
KASSERT(pdpol_state.s_active > 0);
pdpol_state.s_active--;
} else if (pg->pqflags & PQ_INACTIVE) {
TAILQ_REMOVE(&pdpol_state.s_inactiveq, pg, pdqueue);
KASSERT(pdpol_state.s_inactive > 0);
pdpol_state.s_inactive--;
}
pg->pqflags &= ~(PQ_ACTIVE | PQ_INACTIVE | PQ_INTENT_SET);
}
void
uvmpdpol_pagedequeue(struct vm_page *pg)
{
KASSERT(uvm_page_owner_locked_p(pg, true));
KASSERT(mutex_owned(&pg->interlock));
uvmpdpol_set_intent(pg, PQ_INTENT_D);
}
void
uvmpdpol_pageenqueue(struct vm_page *pg)
{
KASSERT(uvm_page_owner_locked_p(pg, false));
KASSERT(mutex_owned(&pg->interlock));
uvmpdpol_set_intent(pg, PQ_INTENT_E);
}
void
uvmpdpol_anfree(struct vm_anon *an)
{
}
bool
uvmpdpol_pageisqueued_p(struct vm_page *pg)
{
uint32_t pqflags;
pqflags = atomic_load_relaxed(&pg->pqflags);
if ((pqflags & PQ_INTENT_SET) != 0) {
return (pqflags & PQ_INTENT_MASK) != PQ_INTENT_D;
} else {
return (pqflags & (PQ_ACTIVE | PQ_INACTIVE)) != 0;
}
}
bool
uvmpdpol_pageactivate_p(struct vm_page *pg)
{
uint32_t pqflags;
pqflags = atomic_load_relaxed(&pg->pqflags);
if ((pqflags & PQ_INTENT_SET) != 0) {
pqflags &= PQ_INTENT_MASK;
return pqflags != PQ_INTENT_A && pqflags != PQ_INTENT_E;
} else {
return true;
}
}
void
uvmpdpol_estimatepageable(int *active, int *inactive)
{
struct uvmpdpol_globalstate *s = &pdpol_state;
if (active) {
*active = s->s_active;
}
if (inactive) {
*inactive = s->s_inactive;
}
}
#if !defined(PDSIM)
static int
min_check(struct uvm_pctparam *pct, int t)
{
struct uvmpdpol_globalstate *s = &pdpol_state;
int total = t;
if (pct != &s->s_anonmin) {
total += uvm_pctparam_get(&s->s_anonmin);
}
if (pct != &s->s_filemin) {
total += uvm_pctparam_get(&s->s_filemin);
}
if (pct != &s->s_execmin) {
total += uvm_pctparam_get(&s->s_execmin);
}
if (total > 95) {
return EINVAL;
}
return 0;
}
#endif
void
uvmpdpol_init(void)
{
struct uvmpdpol_globalstate *s = &pdpol_state;
mutex_init(&s->lock, MUTEX_DEFAULT, IPL_NONE);
TAILQ_INIT(&s->s_activeq);
TAILQ_INIT(&s->s_inactiveq);
uvm_pctparam_init(&s->s_inactivepct, CLOCK_INACTIVEPCT, NULL);
uvm_pctparam_init(&s->s_anonmin, 10, min_check);
uvm_pctparam_init(&s->s_filemin, 10, min_check);
uvm_pctparam_init(&s->s_execmin, 5, min_check);
uvm_pctparam_init(&s->s_anonmax, 80, NULL);
uvm_pctparam_init(&s->s_filemax, 50, NULL);
uvm_pctparam_init(&s->s_execmax, 30, NULL);
}
void
uvmpdpol_init_cpu(struct uvm_cpu *ucpu)
{
ucpu->pdq =
kmem_alloc(CLOCK_PDQ_SIZE * sizeof(struct vm_page *), KM_SLEEP);
ucpu->pdqhead = CLOCK_PDQ_SIZE;
ucpu->pdqtail = CLOCK_PDQ_SIZE;
}
void
uvmpdpol_reinit(void)
{
}
bool
uvmpdpol_needsscan_p(void)
{
return pdpol_state.s_inactive < pdpol_state.s_inactarg;
}
void
uvmpdpol_tune(void)
{
struct uvmpdpol_globalstate *s = &pdpol_state;
mutex_enter(&s->lock);
clock_tune();
mutex_exit(&s->lock);
}
static bool
uvmpdpol_pagerealize_locked(struct vm_page *pg)
{
struct uvmpdpol_globalstate *s __diagused = &pdpol_state;
KASSERT(mutex_owned(&s->lock));
KASSERT(mutex_owned(&pg->interlock));
switch (pg->pqflags & (PQ_INTENT_MASK | PQ_INTENT_SET)) {
case PQ_INTENT_A | PQ_INTENT_SET:
case PQ_INTENT_E | PQ_INTENT_SET:
uvmpdpol_pageactivate_locked(pg);
return true;
case PQ_INTENT_I | PQ_INTENT_SET:
uvmpdpol_pagedeactivate_locked(pg);
return true;
case PQ_INTENT_D | PQ_INTENT_SET:
uvmpdpol_pagedequeue_locked(pg);
return true;
default:
return false;
}
}
static struct uvm_cpu *
uvmpdpol_flush(void)
{
struct uvmpdpol_globalstate *s __diagused = &pdpol_state;
struct uvm_cpu *ucpu;
struct vm_page *pg;
KASSERT(kpreempt_disabled());
mutex_enter(&s->lock);
for (;;) {
ucpu = curcpu()->ci_data.cpu_uvm;
KASSERT(ucpu->pdqhead <= ucpu->pdqtail);
if (__predict_false(ucpu->pdqhead == ucpu->pdqtail)) {
break;
}
pg = ucpu->pdq[ucpu->pdqhead++];
if (__predict_true(ucpu->pdqhead != ucpu->pdqtail)) {
__builtin_prefetch(ucpu->pdq[ucpu->pdqhead]);
}
mutex_enter(&pg->interlock);
pg->pqflags &= ~PQ_INTENT_QUEUED;
(void)uvmpdpol_pagerealize_locked(pg);
mutex_exit(&pg->interlock);
}
mutex_exit(&s->lock);
return ucpu;
}
void
uvmpdpol_pagerealize(struct vm_page *pg)
{
struct uvm_cpu *ucpu;
int s;
s = splsoftbio();
ucpu = curcpu()->ci_data.cpu_uvm;
while (__predict_false(ucpu->pdqhead == 0)) {
ucpu = uvmpdpol_flush();
}
ucpu->pdq[--(ucpu->pdqhead)] = pg;
splx(s);
}
void
uvmpdpol_idle(struct uvm_cpu *ucpu)
{
struct uvmpdpol_globalstate *s = &pdpol_state;
struct vm_page *pg;
int s_spl;
KASSERT(kpreempt_disabled());
if (ucpu->pdqhead == ucpu->pdqtail) {
ucpu->pdqtime = getticks();
return;
}
if (getticks() - ucpu->pdqtime < (hz >> 3)) {
return;
}
if (mutex_tryenter(&s->lock)) {
s_spl = splsoftbio();
while (ucpu->pdqhead != ucpu->pdqtail) {
pg = ucpu->pdq[ucpu->pdqhead];
if (!mutex_tryenter(&pg->interlock)) {
break;
}
ucpu->pdqhead++;
pg->pqflags &= ~PQ_INTENT_QUEUED;
(void)uvmpdpol_pagerealize_locked(pg);
mutex_exit(&pg->interlock);
if (curcpu()->ci_want_resched) {
break;
}
}
if (ucpu->pdqhead == ucpu->pdqtail) {
ucpu->pdqtime = getticks();
}
splx(s_spl);
mutex_exit(&s->lock);
}
}
#if !defined(PDSIM)
#include <sys/sysctl.h>
void
uvmpdpol_sysctlsetup(void)
{
struct uvmpdpol_globalstate *s = &pdpol_state;
uvm_pctparam_createsysctlnode(&s->s_anonmin, "anonmin",
SYSCTL_DESCR("Percentage of physical memory reserved "
"for anonymous application data"));
uvm_pctparam_createsysctlnode(&s->s_filemin, "filemin",
SYSCTL_DESCR("Percentage of physical memory reserved "
"for cached file data"));
uvm_pctparam_createsysctlnode(&s->s_execmin, "execmin",
SYSCTL_DESCR("Percentage of physical memory reserved "
"for cached executable data"));
uvm_pctparam_createsysctlnode(&s->s_anonmax, "anonmax",
SYSCTL_DESCR("Percentage of physical memory which will "
"be reclaimed from other usage for "
"anonymous application data"));
uvm_pctparam_createsysctlnode(&s->s_filemax, "filemax",
SYSCTL_DESCR("Percentage of physical memory which will "
"be reclaimed from other usage for cached "
"file data"));
uvm_pctparam_createsysctlnode(&s->s_execmax, "execmax",
SYSCTL_DESCR("Percentage of physical memory which will "
"be reclaimed from other usage for cached "
"executable data"));
uvm_pctparam_createsysctlnode(&s->s_inactivepct, "inactivepct",
SYSCTL_DESCR("Percentage of inactive queue of "
"the entire (active + inactive) queue"));
}
#endif
#if defined(PDSIM)
void
pdsim_dump(const char *id)
{
#if defined(DEBUG)
#endif
}
#endif