#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/kthread.h>
#include <sys/resourcevar.h>
#include <sys/signalvar.h>
#include <sys/vnode.h>
#include <sys/vmmeter.h>
#include <sys/sysctl.h>
#include <sys/eventhandler.h>
#include <vm/vm.h>
#include <vm/vm_param.h>
#include <sys/lock.h>
#include <vm/vm_object.h>
#include <vm/vm_page.h>
#include <vm/vm_map.h>
#include <vm/vm_pageout.h>
#include <vm/vm_pager.h>
#include <vm/swap_pager.h>
#include <vm/vm_extern.h>
#include <sys/spinlock2.h>
#include <vm/vm_page2.h>
struct swmarker {
struct vm_object dummy_obj;
struct vm_object *save_obj;
vm_ooffset_t save_off;
};
typedef struct swmarker swmarker_t;
static int vm_swapcached_flush (vm_page_t m, int isblkdev);
static int vm_swapcache_test(vm_page_t m);
static int vm_swapcache_writing_heuristic(void);
static int vm_swapcache_writing(vm_page_t marker, int count, int scount);
static void vm_swapcache_cleaning(swmarker_t *marker,
struct vm_object_hash **swindexp);
static void vm_swapcache_movemarker(swmarker_t *marker,
struct vm_object_hash *swindex, vm_object_t object);
struct thread *swapcached_thread;
SYSCTL_NODE(_vm, OID_AUTO, swapcache, CTLFLAG_RW, NULL, NULL);
int vm_swapcache_read_enable;
static long vm_swapcache_wtrigger;
static int vm_swapcache_sleep;
static int vm_swapcache_maxscan = PQ_L2_SIZE * 8;
static int vm_swapcache_maxlaunder = PQ_L2_SIZE * 4;
static int vm_swapcache_data_enable = 0;
static int vm_swapcache_meta_enable = 0;
static int vm_swapcache_maxswappct = 75;
static int vm_swapcache_hysteresis;
static int vm_swapcache_min_hysteresis;
int vm_swapcache_use_chflags = 0;
static int64_t vm_swapcache_minburst = 10000000LL;
static int64_t vm_swapcache_curburst = 4000000000LL;
static int64_t vm_swapcache_maxburst = 2000000000LL;
static int64_t vm_swapcache_accrate = 100000LL;
static int64_t vm_swapcache_write_count;
static int64_t vm_swapcache_maxfilesize;
static int64_t vm_swapcache_cleanperobj = 16*1024*1024;
SYSCTL_INT(_vm_swapcache, OID_AUTO, maxlaunder,
CTLFLAG_RW, &vm_swapcache_maxlaunder, 0, "");
SYSCTL_INT(_vm_swapcache, OID_AUTO, maxscan,
CTLFLAG_RW, &vm_swapcache_maxscan, 0, "");
SYSCTL_INT(_vm_swapcache, OID_AUTO, data_enable,
CTLFLAG_RW, &vm_swapcache_data_enable, 0, "");
SYSCTL_INT(_vm_swapcache, OID_AUTO, meta_enable,
CTLFLAG_RW, &vm_swapcache_meta_enable, 0, "");
SYSCTL_INT(_vm_swapcache, OID_AUTO, read_enable,
CTLFLAG_RW, &vm_swapcache_read_enable, 0, "");
SYSCTL_INT(_vm_swapcache, OID_AUTO, maxswappct,
CTLFLAG_RW, &vm_swapcache_maxswappct, 0, "");
SYSCTL_INT(_vm_swapcache, OID_AUTO, hysteresis,
CTLFLAG_RD, &vm_swapcache_hysteresis, 0, "");
SYSCTL_INT(_vm_swapcache, OID_AUTO, min_hysteresis,
CTLFLAG_RW, &vm_swapcache_min_hysteresis, 0, "");
SYSCTL_INT(_vm_swapcache, OID_AUTO, use_chflags,
CTLFLAG_RW, &vm_swapcache_use_chflags, 0, "");
SYSCTL_QUAD(_vm_swapcache, OID_AUTO, minburst,
CTLFLAG_RW, &vm_swapcache_minburst, 0, "");
SYSCTL_QUAD(_vm_swapcache, OID_AUTO, curburst,
CTLFLAG_RW, &vm_swapcache_curburst, 0, "");
SYSCTL_QUAD(_vm_swapcache, OID_AUTO, maxburst,
CTLFLAG_RW, &vm_swapcache_maxburst, 0, "");
SYSCTL_QUAD(_vm_swapcache, OID_AUTO, maxfilesize,
CTLFLAG_RW, &vm_swapcache_maxfilesize, 0, "");
SYSCTL_QUAD(_vm_swapcache, OID_AUTO, accrate,
CTLFLAG_RW, &vm_swapcache_accrate, 0, "");
SYSCTL_QUAD(_vm_swapcache, OID_AUTO, write_count,
CTLFLAG_RW, &vm_swapcache_write_count, 0, "");
SYSCTL_QUAD(_vm_swapcache, OID_AUTO, cleanperobj,
CTLFLAG_RW, &vm_swapcache_cleanperobj, 0, "");
#define SWAPMAX(adj) \
((int64_t)vm_swap_max * (vm_swapcache_maxswappct + (adj)) / 100)
static void
shutdown_swapcache(void *arg __unused)
{
vm_swapcache_read_enable = 0;
vm_swapcache_data_enable = 0;
vm_swapcache_meta_enable = 0;
wakeup(&vm_swapcache_sleep);
}
static void
vm_swapcached_thread(void)
{
enum { SWAPC_WRITING, SWAPC_CLEANING } state = SWAPC_WRITING;
enum { SWAPB_BURSTING, SWAPB_RECOVERING } burst = SWAPB_BURSTING;
static struct vm_page page_marker[PQ_L2_SIZE];
static swmarker_t swmarker;
static struct vm_object_hash *swindex;
int q;
curthread->td_flags |= TDF_SYSTHREAD;
EVENTHANDLER_REGISTER(shutdown_pre_sync, shutdown_kproc,
swapcached_thread, SHUTDOWN_PRI_FIRST);
EVENTHANDLER_REGISTER(shutdown_pre_sync, shutdown_swapcache,
NULL, SHUTDOWN_PRI_SECOND);
bzero(&page_marker, sizeof(page_marker));
for (q = 0; q < PQ_L2_SIZE; ++q) {
page_marker[q].flags = PG_FICTITIOUS | PG_MARKER;
page_marker[q].busy_count = PBUSY_LOCKED;
page_marker[q].queue = PQ_INACTIVE + q;
page_marker[q].pc = q;
page_marker[q].wire_count = 1;
vm_page_queues_spin_lock(PQ_INACTIVE + q);
TAILQ_INSERT_HEAD(
&vm_page_queues[PQ_INACTIVE + q].pl,
&page_marker[q], pageq);
vm_page_queues_spin_unlock(PQ_INACTIVE + q);
}
vm_swapcache_min_hysteresis = 1024;
vm_swapcache_hysteresis = vm_swapcache_min_hysteresis;
vm_swapcache_wtrigger = -vm_swapcache_hysteresis;
bzero(&swmarker, sizeof(swmarker));
swmarker.dummy_obj.type = OBJT_MARKER;
swindex = &vm_object_hash[0];
lwkt_gettoken(&swindex->token);
TAILQ_INSERT_HEAD(&swindex->list, &swmarker.dummy_obj, object_entry);
lwkt_reltoken(&swindex->token);
for (;;) {
int reached_end;
int scount;
int count;
kproc_suspend_loop();
if ((vm_swapcache_data_enable == 0 &&
vm_swapcache_meta_enable == 0 &&
vm_swap_cache_use <= SWAPMAX(0)) ||
vm_swap_max == 0) {
tsleep(&vm_swapcache_sleep, 0, "csleep", hz * 5);
continue;
}
tsleep(&vm_swapcache_sleep, 0, "csleep", hz / 10);
if (state == SWAPC_WRITING) {
if (vm_swap_cache_use > SWAPMAX(0))
state = SWAPC_CLEANING;
} else {
if (vm_swap_cache_use < SWAPMAX(-10))
state = SWAPC_WRITING;
}
if (vm_swapcache_curburst < vm_swapcache_maxburst) {
vm_swapcache_curburst += vm_swapcache_accrate / 10;
if (vm_swapcache_curburst > vm_swapcache_maxburst)
vm_swapcache_curburst = vm_swapcache_maxburst;
}
if (state != SWAPC_WRITING) {
vm_swapcache_cleaning(&swmarker, &swindex);
continue;
}
if (vm_swapcache_curburst < vm_swapcache_accrate)
continue;
reached_end = 0;
count = vm_swapcache_maxlaunder / PQ_L2_SIZE + 2;
scount = vm_swapcache_maxscan / PQ_L2_SIZE + 2;
if (burst == SWAPB_BURSTING) {
if (vm_swapcache_writing_heuristic()) {
for (q = 0; q < PQ_L2_SIZE; ++q) {
reached_end +=
vm_swapcache_writing(
&page_marker[q],
count,
scount);
}
}
if (vm_swapcache_curburst <= 0)
burst = SWAPB_RECOVERING;
} else if (vm_swapcache_curburst > vm_swapcache_minburst) {
if (vm_swapcache_writing_heuristic()) {
for (q = 0; q < PQ_L2_SIZE; ++q) {
reached_end +=
vm_swapcache_writing(
&page_marker[q],
count,
scount);
}
}
burst = SWAPB_BURSTING;
}
if (reached_end == PQ_L2_SIZE) {
vm_swapcache_wtrigger = -vm_swapcache_hysteresis;
}
}
for (q = 0; q < PQ_L2_SIZE; ++q) {
vm_page_queues_spin_lock(PQ_INACTIVE + q);
TAILQ_REMOVE(
&vm_page_queues[PQ_INACTIVE + q].pl,
&page_marker[q], pageq);
vm_page_queues_spin_unlock(PQ_INACTIVE + q);
}
lwkt_gettoken(&swindex->token);
TAILQ_REMOVE(&swindex->list, &swmarker.dummy_obj, object_entry);
lwkt_reltoken(&swindex->token);
}
static struct kproc_desc swpc_kp = {
"swapcached",
vm_swapcached_thread,
&swapcached_thread
};
SYSINIT(swapcached, SI_SUB_KTHREAD_PAGE, SI_ORDER_SECOND, kproc_start, &swpc_kp);
static int
vm_swapcache_writing_heuristic(void)
{
int hyst;
int q;
long adds;
hyst = vmstats.v_inactive_count / 4;
if (hyst < vm_swapcache_min_hysteresis)
hyst = vm_swapcache_min_hysteresis;
cpu_ccfence();
vm_swapcache_hysteresis = hyst;
adds = 0;
for (q = PQ_INACTIVE; q < PQ_INACTIVE + PQ_L2_SIZE; ++q) {
adds += atomic_swap_long(&vm_page_queues[q].adds, 0);
}
vm_swapcache_wtrigger += adds;
if (vm_swapcache_wtrigger < -hyst)
vm_swapcache_wtrigger = -hyst;
return (vm_swapcache_wtrigger >= 0);
}
static int
vm_swapcache_writing(vm_page_t marker, int count, int scount)
{
vm_object_t object;
struct vnode *vp;
vm_page_t m;
int isblkdev;
vm_page_queues_spin_lock(marker->queue);
while ((m = TAILQ_NEXT(marker, pageq)) != NULL &&
count > 0 && scount-- > 0) {
KKASSERT(m->queue == marker->queue);
if (panicstr || dumping)
break;
if (vm_swapcache_curburst < 0)
break;
TAILQ_REMOVE(
&vm_page_queues[marker->queue].pl, marker, pageq);
TAILQ_INSERT_AFTER(
&vm_page_queues[marker->queue].pl, m, marker, pageq);
if (m->flags & (PG_MARKER | PG_SWAPPED))
continue;
if (vm_page_busy_try(m, TRUE))
continue;
vm_page_queues_spin_unlock(marker->queue);
if ((object = m->object) == NULL) {
vm_page_wakeup(m);
vm_page_queues_spin_lock(marker->queue);
continue;
}
vm_object_hold(object);
if (m->object != object) {
vm_object_drop(object);
vm_page_wakeup(m);
vm_page_queues_spin_lock(marker->queue);
continue;
}
if (vm_swapcache_test(m)) {
vm_object_drop(object);
vm_page_wakeup(m);
vm_page_queues_spin_lock(marker->queue);
continue;
}
vp = object->handle;
if (vp == NULL) {
vm_object_drop(object);
vm_page_wakeup(m);
vm_page_queues_spin_lock(marker->queue);
continue;
}
switch(vp->v_type) {
case VREG:
if (m->flags & PG_NOTMETA) {
vm_object_drop(object);
vm_page_wakeup(m);
vm_page_queues_spin_lock(marker->queue);
continue;
}
if (vm_swapcache_data_enable == 0 ||
((vp->v_flag & VSWAPCACHE) == 0 &&
vm_swapcache_use_chflags)) {
vm_object_drop(object);
vm_page_wakeup(m);
vm_page_queues_spin_lock(marker->queue);
continue;
}
if (vm_swapcache_maxfilesize &&
object->size >
(vm_swapcache_maxfilesize >> PAGE_SHIFT)) {
vm_object_drop(object);
vm_page_wakeup(m);
vm_page_queues_spin_lock(marker->queue);
continue;
}
isblkdev = 0;
break;
case VCHR:
if (m->flags & PG_NOTMETA) {
vm_object_drop(object);
vm_page_wakeup(m);
vm_page_queues_spin_lock(marker->queue);
continue;
}
if (vm_swapcache_meta_enable == 0) {
vm_object_drop(object);
vm_page_wakeup(m);
vm_page_queues_spin_lock(marker->queue);
continue;
}
isblkdev = 1;
break;
default:
vm_object_drop(object);
vm_page_wakeup(m);
vm_page_queues_spin_lock(marker->queue);
continue;
}
count -= vm_swapcached_flush(m, isblkdev);
vm_object_drop(object);
vm_page_queues_spin_lock(marker->queue);
}
vm_page_queues_spin_unlock(marker->queue);
return (m == NULL);
}
static
int
vm_swapcached_flush(vm_page_t m, int isblkdev)
{
vm_object_t object;
vm_page_t marray[SWAP_META_PAGES];
vm_pindex_t basei;
int rtvals[SWAP_META_PAGES];
int x;
int i;
int j;
int count;
int error;
vm_page_io_start(m);
vm_page_protect(m, VM_PROT_READ);
object = m->object;
vm_object_hold(object);
x = (int)m->pindex & SWAP_META_MASK;
marray[x] = m;
basei = m->pindex;
vm_page_wakeup(m);
for (i = x - 1; i >= 0; --i) {
m = vm_page_lookup_busy_try(object, basei - x + i,
TRUE, &error);
if (error || m == NULL)
break;
if (vm_swapcache_test(m)) {
vm_page_wakeup(m);
break;
}
if (isblkdev && (m->flags & PG_NOTMETA)) {
vm_page_wakeup(m);
break;
}
vm_page_io_start(m);
vm_page_protect(m, VM_PROT_READ);
if (m->queue - m->pc == PQ_CACHE) {
vm_page_unqueue_nowakeup(m);
vm_page_deactivate(m);
}
marray[i] = m;
vm_page_wakeup(m);
}
++i;
for (j = x + 1; j < SWAP_META_PAGES; ++j) {
m = vm_page_lookup_busy_try(object, basei - x + j,
TRUE, &error);
if (error || m == NULL)
break;
if (vm_swapcache_test(m)) {
vm_page_wakeup(m);
break;
}
if (isblkdev && (m->flags & PG_NOTMETA)) {
vm_page_wakeup(m);
break;
}
vm_page_io_start(m);
vm_page_protect(m, VM_PROT_READ);
if (m->queue - m->pc == PQ_CACHE) {
vm_page_unqueue_nowakeup(m);
vm_page_deactivate(m);
}
marray[j] = m;
vm_page_wakeup(m);
}
count = j - i;
vm_object_pip_add(object, count);
swap_pager_putpages(object, marray + i, count, FALSE, rtvals + i);
vm_swapcache_write_count += count * PAGE_SIZE;
vm_swapcache_curburst -= count * PAGE_SIZE;
while (i < j) {
if (rtvals[i] != VM_PAGER_PEND) {
vm_page_busy_wait(marray[i], FALSE, "swppgfd");
vm_page_io_finish(marray[i]);
vm_page_wakeup(marray[i]);
vm_object_pip_wakeup(object);
}
++i;
}
vm_object_drop(object);
return(count);
}
static int
vm_swapcache_test(vm_page_t m)
{
vm_object_t object;
if (m->flags & (PG_UNQUEUED | PG_FICTITIOUS))
return(1);
if (m->hold_count || m->wire_count)
return(1);
if (m->valid != VM_PAGE_BITS_ALL)
return(1);
if (m->dirty & m->valid)
return(1);
if ((object = m->object) == NULL)
return(1);
if (object->type != OBJT_VNODE ||
(object->flags & OBJ_DEAD)) {
return(1);
}
vm_page_test_dirty(m);
if (m->dirty & m->valid)
return(1);
return(0);
}
static
void
vm_swapcache_cleaning(swmarker_t *marker, struct vm_object_hash **swindexp)
{
vm_object_t object;
struct vnode *vp;
int count;
int scount;
int n;
int didmove;
count = vm_swapcache_maxlaunder;
scount = vm_swapcache_maxscan;
lwkt_gettoken(&(*swindexp)->token);
didmove = 0;
outerloop:
while ((object = TAILQ_NEXT(&marker->dummy_obj,
object_entry)) != NULL) {
if (object->type == OBJT_MARKER) {
vm_swapcache_movemarker(marker, *swindexp, object);
didmove = 1;
continue;
}
if (--scount <= 0)
goto breakout;
vm_object_hold(object);
lwkt_yield();
if ((object->type != OBJT_VNODE) ||
((object->flags & OBJ_DEAD) ||
object->swblock_count == 0) ||
((vp = object->handle) == NULL) ||
(vp->v_type != VREG && vp->v_type != VCHR)) {
vm_object_drop(object);
vm_swapcache_movemarker(marker, *swindexp, object);
didmove = 1;
continue;
}
if (marker->save_obj != object || didmove) {
marker->dummy_obj.size = 0;
marker->save_off = 0;
marker->save_obj = object;
didmove = 0;
}
lwkt_token_swap();
lwkt_reltoken(&(*swindexp)->token);
n = swap_pager_condfree(object, &marker->dummy_obj.size,
(count + SWAP_META_MASK) & ~SWAP_META_MASK);
vm_object_drop(object);
lwkt_gettoken(&(*swindexp)->token);
if (n <= 0 ||
marker->save_off > vm_swapcache_cleanperobj) {
vm_swapcache_movemarker(marker, *swindexp, object);
didmove = 1;
}
count -= n;
marker->save_off += n * PAGE_SIZE;
if (count < 0)
goto breakout;
}
TAILQ_REMOVE(&(*swindexp)->list, &marker->dummy_obj, object_entry);
lwkt_reltoken(&(*swindexp)->token);
if (++*swindexp >= &vm_object_hash[VMOBJ_HSIZE])
*swindexp = &vm_object_hash[0];
lwkt_gettoken(&(*swindexp)->token);
TAILQ_INSERT_HEAD(&(*swindexp)->list, &marker->dummy_obj, object_entry);
if (*swindexp != &vm_object_hash[0])
goto outerloop;
breakout:
lwkt_reltoken(&(*swindexp)->token);
}
static void
vm_swapcache_movemarker(swmarker_t *marker, struct vm_object_hash *swindex,
vm_object_t object)
{
if (TAILQ_NEXT(&marker->dummy_obj, object_entry) == object) {
TAILQ_REMOVE(&swindex->list, &marker->dummy_obj, object_entry);
TAILQ_INSERT_AFTER(&swindex->list, object,
&marker->dummy_obj, object_entry);
}
}