#include "opt_vm.h"
#define EXTERR_CATEGORY EXTERR_CAT_SWAP
#include <sys/param.h>
#include <sys/bio.h>
#include <sys/blist.h>
#include <sys/buf.h>
#include <sys/conf.h>
#include <sys/disk.h>
#include <sys/disklabel.h>
#include <sys/eventhandler.h>
#include <sys/exterrvar.h>
#include <sys/fcntl.h>
#include <sys/limits.h>
#include <sys/lock.h>
#include <sys/kernel.h>
#include <sys/mount.h>
#include <sys/namei.h>
#include <sys/malloc.h>
#include <sys/pctrie.h>
#include <sys/priv.h>
#include <sys/proc.h>
#include <sys/racct.h>
#include <sys/resource.h>
#include <sys/resourcevar.h>
#include <sys/rwlock.h>
#include <sys/sbuf.h>
#include <sys/sysctl.h>
#include <sys/sysproto.h>
#include <sys/systm.h>
#include <sys/sx.h>
#include <sys/unistd.h>
#include <sys/user.h>
#include <sys/vmmeter.h>
#include <sys/vnode.h>
#include <security/mac/mac_framework.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#include <vm/vm_map.h>
#include <vm/vm_kern.h>
#include <vm/vm_object.h>
#include <vm/vm_page.h>
#include <vm/vm_pager.h>
#include <vm/vm_pageout.h>
#include <vm/vm_param.h>
#include <vm/vm_radix.h>
#include <vm/swap_pager.h>
#include <vm/vm_extern.h>
#include <vm/uma.h>
#include <geom/geom.h>
#ifndef MAX_PAGEOUT_CLUSTER
#define MAX_PAGEOUT_CLUSTER 32
#endif
#if !defined(SWB_NPAGES)
#define SWB_NPAGES MAX_PAGEOUT_CLUSTER
#endif
#define SWAP_META_PAGES PCTRIE_COUNT
struct swblk {
vm_pindex_t p;
daddr_t d[SWAP_META_PAGES];
};
struct page_range {
daddr_t start;
daddr_t num;
};
static MALLOC_DEFINE(M_VMPGDATA, "vm_pgdata", "swap pager private data");
static struct mtx sw_dev_mtx;
static TAILQ_HEAD(, swdevt) swtailq = TAILQ_HEAD_INITIALIZER(swtailq);
static struct swdevt *swdevhd;
static int nswapdev;
int swap_pager_avail;
static struct sx swdev_syscall_lock;
static __exclusive_cache_line u_long swap_reserved;
static u_long swap_total;
static int sysctl_page_shift(SYSCTL_HANDLER_ARGS);
static SYSCTL_NODE(_vm_stats, OID_AUTO, swap, CTLFLAG_RD | CTLFLAG_MPSAFE, 0,
"VM swap stats");
SYSCTL_PROC(_vm, OID_AUTO, swap_reserved, CTLTYPE_U64 | CTLFLAG_RD | CTLFLAG_MPSAFE,
&swap_reserved, 0, sysctl_page_shift, "QU",
"Amount of swap storage needed to back all allocated anonymous memory.");
SYSCTL_PROC(_vm, OID_AUTO, swap_total, CTLTYPE_U64 | CTLFLAG_RD | CTLFLAG_MPSAFE,
&swap_total, 0, sysctl_page_shift, "QU",
"Total amount of available swap storage.");
int vm_overcommit __read_mostly = 0;
SYSCTL_INT(_vm, VM_OVERCOMMIT, overcommit, CTLFLAG_RW, &vm_overcommit, 0,
"Configure virtual memory overcommit behavior. See tuning(7) "
"for details.");
static unsigned long swzone;
SYSCTL_ULONG(_vm, OID_AUTO, swzone, CTLFLAG_RD, &swzone, 0,
"Actual size of swap metadata zone");
static unsigned long swap_maxpages;
SYSCTL_ULONG(_vm, OID_AUTO, swap_maxpages, CTLFLAG_RD, &swap_maxpages, 0,
"Maximum amount of swap supported");
static COUNTER_U64_DEFINE_EARLY(swap_free_deferred);
SYSCTL_COUNTER_U64(_vm_stats_swap, OID_AUTO, free_deferred,
CTLFLAG_RD, &swap_free_deferred,
"Number of pages that deferred freeing swap space");
static COUNTER_U64_DEFINE_EARLY(swap_free_completed);
SYSCTL_COUNTER_U64(_vm_stats_swap, OID_AUTO, free_completed,
CTLFLAG_RD, &swap_free_completed,
"Number of deferred frees completed");
static int
sysctl_page_shift(SYSCTL_HANDLER_ARGS)
{
uint64_t newval;
u_long value = *(u_long *)arg1;
newval = ((uint64_t)value) << PAGE_SHIFT;
return (sysctl_handle_64(oidp, &newval, 0, req));
}
static bool
swap_reserve_by_cred_rlimit(u_long pincr, struct ucred *cred, int oc)
{
struct uidinfo *uip;
u_long prev;
uip = cred->cr_ruidinfo;
prev = atomic_fetchadd_long(&uip->ui_vmsize, pincr);
if ((oc & SWAP_RESERVE_RLIMIT_ON) != 0 &&
prev + pincr > lim_cur(curthread, RLIMIT_SWAP) &&
priv_check(curthread, PRIV_VM_SWAP_NORLIMIT) != 0) {
prev = atomic_fetchadd_long(&uip->ui_vmsize, -pincr);
KASSERT(prev >= pincr,
("negative vmsize for uid %d\n", uip->ui_uid));
return (false);
}
return (true);
}
static void
swap_release_by_cred_rlimit(u_long pdecr, struct ucred *cred)
{
struct uidinfo *uip;
#ifdef INVARIANTS
u_long prev;
#endif
uip = cred->cr_ruidinfo;
#ifdef INVARIANTS
prev = atomic_fetchadd_long(&uip->ui_vmsize, -pdecr);
KASSERT(prev >= pdecr,
("negative vmsize for uid %d\n", uip->ui_uid));
#else
atomic_subtract_long(&uip->ui_vmsize, pdecr);
#endif
}
static void
swap_reserve_force_rlimit(u_long pincr, struct ucred *cred)
{
struct uidinfo *uip;
uip = cred->cr_ruidinfo;
atomic_add_long(&uip->ui_vmsize, pincr);
}
bool
swap_reserve(vm_ooffset_t incr)
{
return (swap_reserve_by_cred(incr, curthread->td_ucred));
}
bool
swap_reserve_by_cred(vm_ooffset_t incr, struct ucred *cred)
{
u_long r, s, prev, pincr;
#ifdef RACCT
int error;
#endif
int oc;
static int curfail;
static struct timeval lastfail;
KASSERT((incr & PAGE_MASK) == 0, ("%s: incr: %ju & PAGE_MASK",
__func__, (uintmax_t)incr));
#ifdef RACCT
if (RACCT_ENABLED()) {
PROC_LOCK(curproc);
error = racct_add(curproc, RACCT_SWAP, incr);
PROC_UNLOCK(curproc);
if (error != 0)
return (false);
}
#endif
pincr = atop(incr);
prev = atomic_fetchadd_long(&swap_reserved, pincr);
r = prev + pincr;
s = swap_total;
oc = atomic_load_int(&vm_overcommit);
if (r > s && (oc & SWAP_RESERVE_ALLOW_NONWIRED) != 0) {
s += vm_cnt.v_page_count - vm_cnt.v_free_reserved -
vm_wire_count();
}
if ((oc & SWAP_RESERVE_FORCE_ON) != 0 && r > s &&
priv_check(curthread, PRIV_VM_SWAP_NOQUOTA) != 0) {
prev = atomic_fetchadd_long(&swap_reserved, -pincr);
KASSERT(prev >= pincr,
("swap_reserved < incr on overcommit fail"));
goto out_error;
}
if (!swap_reserve_by_cred_rlimit(pincr, cred, oc)) {
prev = atomic_fetchadd_long(&swap_reserved, -pincr);
KASSERT(prev >= pincr,
("swap_reserved < incr on overcommit fail"));
goto out_error;
}
return (true);
out_error:
if (ppsratecheck(&lastfail, &curfail, 1)) {
printf("uid %d, pid %d: swap reservation "
"for %jd bytes failed\n",
cred->cr_ruidinfo->ui_uid, curproc->p_pid, incr);
}
#ifdef RACCT
if (RACCT_ENABLED()) {
PROC_LOCK(curproc);
racct_sub(curproc, RACCT_SWAP, incr);
PROC_UNLOCK(curproc);
}
#endif
return (false);
}
void
swap_reserve_force(vm_ooffset_t incr)
{
u_long pincr;
KASSERT((incr & PAGE_MASK) == 0, ("%s: incr: %ju & PAGE_MASK",
__func__, (uintmax_t)incr));
#ifdef RACCT
if (RACCT_ENABLED()) {
PROC_LOCK(curproc);
racct_add_force(curproc, RACCT_SWAP, incr);
PROC_UNLOCK(curproc);
}
#endif
pincr = atop(incr);
atomic_add_long(&swap_reserved, pincr);
swap_reserve_force_rlimit(pincr, curthread->td_ucred);
}
void
swap_release(vm_ooffset_t decr)
{
struct ucred *cred;
PROC_LOCK(curproc);
cred = curproc->p_ucred;
swap_release_by_cred(decr, cred);
PROC_UNLOCK(curproc);
}
void
swap_release_by_cred(vm_ooffset_t decr, struct ucred *cred)
{
u_long pdecr;
#ifdef INVARIANTS
u_long prev;
#endif
KASSERT((decr & PAGE_MASK) == 0, ("%s: decr: %ju & PAGE_MASK",
__func__, (uintmax_t)decr));
pdecr = atop(decr);
#ifdef INVARIANTS
prev = atomic_fetchadd_long(&swap_reserved, -pdecr);
KASSERT(prev >= pdecr, ("swap_reserved < decr"));
#else
atomic_subtract_long(&swap_reserved, pdecr);
#endif
swap_release_by_cred_rlimit(pdecr, cred);
#ifdef RACCT
if (racct_enable)
racct_sub_cred(cred, RACCT_SWAP, decr);
#endif
}
static bool swap_pager_full = true;
bool swap_pager_almost_full = true;
static struct mtx swbuf_mtx;
static int nsw_wcount_async;
static int nsw_wcount_async_max;
int nsw_cluster_max;
static int sysctl_swap_async_max(SYSCTL_HANDLER_ARGS);
SYSCTL_PROC(_vm, OID_AUTO, swap_async_max, CTLTYPE_INT | CTLFLAG_RW |
CTLFLAG_MPSAFE, NULL, 0, sysctl_swap_async_max, "I",
"Maximum running async swap ops");
static int sysctl_swap_fragmentation(SYSCTL_HANDLER_ARGS);
SYSCTL_PROC(_vm, OID_AUTO, swap_fragmentation, CTLTYPE_STRING | CTLFLAG_RD |
CTLFLAG_MPSAFE, NULL, 0, sysctl_swap_fragmentation, "A",
"Swap Fragmentation Info");
static struct sx sw_alloc_sx;
#define NOBJLISTS 8
#define NOBJLIST(handle) \
(&swap_pager_object_list[((int)(intptr_t)handle >> 4) & (NOBJLISTS-1)])
static struct pagerlst swap_pager_object_list[NOBJLISTS];
static uma_zone_t swwbuf_zone;
static uma_zone_t swrbuf_zone;
static uma_zone_t swblk_zone;
static uma_zone_t swpctrie_zone;
static vm_object_t
swap_pager_alloc(void *handle, vm_ooffset_t size,
vm_prot_t prot, vm_ooffset_t offset, struct ucred *);
static void swap_pager_dealloc(vm_object_t object);
static int swap_pager_getpages(vm_object_t, vm_page_t *, int, int *,
int *);
static int swap_pager_getpages_async(vm_object_t, vm_page_t *, int, int *,
int *, pgo_getpages_iodone_t, void *);
static void swap_pager_putpages(vm_object_t, vm_page_t *, int, int, int *);
static boolean_t
swap_pager_haspage(vm_object_t object, vm_pindex_t pindex, int *before, int *after);
static void swap_pager_init(void);
static void swap_pager_unswapped(vm_page_t);
static void swap_pager_swapoff(struct swdevt *sp);
static void swap_pager_update_writecount(vm_object_t object,
vm_offset_t start, vm_offset_t end);
static void swap_pager_release_writecount(vm_object_t object,
vm_offset_t start, vm_offset_t end);
static void swap_pager_freespace_pgo(vm_object_t object, vm_pindex_t start,
vm_size_t size);
const struct pagerops swappagerops = {
.pgo_kvme_type = KVME_TYPE_SWAP,
.pgo_init = swap_pager_init,
.pgo_alloc = swap_pager_alloc,
.pgo_dealloc = swap_pager_dealloc,
.pgo_getpages = swap_pager_getpages,
.pgo_getpages_async = swap_pager_getpages_async,
.pgo_putpages = swap_pager_putpages,
.pgo_haspage = swap_pager_haspage,
.pgo_pageunswapped = swap_pager_unswapped,
.pgo_update_writecount = swap_pager_update_writecount,
.pgo_release_writecount = swap_pager_release_writecount,
.pgo_freespace = swap_pager_freespace_pgo,
};
static int nswap_lowat = 128;
static int nswap_hiwat = 512;
SYSCTL_INT(_vm, OID_AUTO, dmmax, CTLFLAG_RD, &nsw_cluster_max, 0,
"Maximum size of a swap block in pages");
static void swp_sizecheck(void);
static void swp_pager_async_iodone(struct buf *bp);
static bool swp_pager_swblk_empty(struct swblk *sb, int start, int limit);
static void swp_pager_free_empty_swblk(vm_object_t, struct swblk *sb);
static int swapongeom(struct vnode *);
static int swaponvp(struct thread *, struct vnode *, u_long);
static int swapoff_one(struct swdevt *sp, struct ucred *cred,
u_int flags);
static void swp_pager_freeswapspace(const struct page_range *range);
static daddr_t swp_pager_getswapspace(int *npages);
static daddr_t swp_pager_meta_build(struct pctrie_iter *, vm_object_t object,
vm_pindex_t, daddr_t, bool);
static void swp_pager_meta_free(vm_object_t, vm_pindex_t, vm_pindex_t,
vm_size_t *);
static void swp_pager_meta_transfer(vm_object_t src, vm_object_t dst,
vm_pindex_t pindex, vm_pindex_t count);
static void swp_pager_meta_free_all(vm_object_t);
static daddr_t swp_pager_meta_lookup(struct pctrie_iter *, vm_pindex_t);
static void
swp_pager_init_freerange(struct page_range *range)
{
range->start = SWAPBLK_NONE;
range->num = 0;
}
static void
swp_pager_update_freerange(struct page_range *range, daddr_t addr)
{
if (range->start + range->num == addr) {
range->num++;
} else {
swp_pager_freeswapspace(range);
range->start = addr;
range->num = 1;
}
}
static void *
swblk_trie_alloc(struct pctrie *ptree)
{
return (uma_zalloc(swpctrie_zone, M_NOWAIT | (curproc == pageproc ?
M_USE_RESERVE : 0)));
}
static void
swblk_trie_free(struct pctrie *ptree, void *node)
{
uma_zfree(swpctrie_zone, node);
}
static int
swblk_start(struct swblk *sb, vm_pindex_t pindex)
{
return (sb == NULL || sb->p >= pindex ?
0 : pindex % SWAP_META_PAGES);
}
PCTRIE_DEFINE(SWAP, swblk, p, swblk_trie_alloc, swblk_trie_free);
static struct swblk *
swblk_lookup(vm_object_t object, vm_pindex_t pindex)
{
return (SWAP_PCTRIE_LOOKUP(&object->un_pager.swp.swp_blks,
rounddown(pindex, SWAP_META_PAGES)));
}
static void
swblk_lookup_remove(vm_object_t object, struct swblk *sb)
{
SWAP_PCTRIE_REMOVE(&object->un_pager.swp.swp_blks, sb->p);
}
static bool
swblk_is_empty(vm_object_t object)
{
return (pctrie_is_empty(&object->un_pager.swp.swp_blks));
}
static struct swblk *
swblk_iter_lookup_ge(struct pctrie_iter *blks, vm_pindex_t pindex)
{
return (SWAP_PCTRIE_ITER_LOOKUP_GE(blks,
rounddown(pindex, SWAP_META_PAGES)));
}
static void
swblk_iter_init_only(struct pctrie_iter *blks, vm_object_t object)
{
VM_OBJECT_ASSERT_LOCKED(object);
MPASS((object->flags & OBJ_SWAP) != 0);
pctrie_iter_init(blks, &object->un_pager.swp.swp_blks);
}
static struct swblk *
swblk_iter_init(struct pctrie_iter *blks, vm_object_t object,
vm_pindex_t pindex)
{
swblk_iter_init_only(blks, object);
return (swblk_iter_lookup_ge(blks, pindex));
}
static struct swblk *
swblk_iter_reinit(struct pctrie_iter *blks, vm_object_t object,
vm_pindex_t pindex)
{
swblk_iter_init_only(blks, object);
return (SWAP_PCTRIE_ITER_LOOKUP(blks,
rounddown(pindex, SWAP_META_PAGES)));
}
static struct swblk *
swblk_iter_limit_init(struct pctrie_iter *blks, vm_object_t object,
vm_pindex_t pindex, vm_pindex_t limit)
{
VM_OBJECT_ASSERT_LOCKED(object);
MPASS((object->flags & OBJ_SWAP) != 0);
pctrie_iter_limit_init(blks, &object->un_pager.swp.swp_blks, limit);
return (swblk_iter_lookup_ge(blks, pindex));
}
static struct swblk *
swblk_iter_next(struct pctrie_iter *blks)
{
return (SWAP_PCTRIE_ITER_JUMP_GE(blks, SWAP_META_PAGES));
}
static struct swblk *
swblk_iter_lookup(struct pctrie_iter *blks, vm_pindex_t pindex)
{
return (SWAP_PCTRIE_ITER_LOOKUP(blks,
rounddown(pindex, SWAP_META_PAGES)));
}
static int
swblk_iter_insert(struct pctrie_iter *blks, struct swblk *sb)
{
return (SWAP_PCTRIE_ITER_INSERT(blks, sb));
}
static void
swblk_iter_remove(struct pctrie_iter *blks)
{
SWAP_PCTRIE_ITER_REMOVE(blks);
}
static void
swp_sizecheck(void)
{
if (swap_pager_avail < nswap_lowat) {
if (!swap_pager_almost_full) {
printf("swap_pager: out of swap space\n");
swap_pager_almost_full = true;
}
} else {
swap_pager_full = false;
if (swap_pager_avail > nswap_hiwat)
swap_pager_almost_full = false;
}
}
static void
swap_pager_init(void)
{
int i;
for (i = 0; i < NOBJLISTS; ++i)
TAILQ_INIT(&swap_pager_object_list[i]);
mtx_init(&sw_dev_mtx, "swapdev", NULL, MTX_DEF);
sx_init(&sw_alloc_sx, "swspsx");
sx_init(&swdev_syscall_lock, "swsysc");
nsw_cluster_max = min(maxphys / PAGE_SIZE, MAX_PAGEOUT_CLUSTER);
}
void
swap_pager_swap_init(void)
{
unsigned long n, n2;
nsw_wcount_async = 4;
nsw_wcount_async_max = nsw_wcount_async;
mtx_init(&swbuf_mtx, "async swbuf mutex", NULL, MTX_DEF);
swwbuf_zone = pbuf_zsecond_create("swwbuf", nswbuf / 4);
swrbuf_zone = pbuf_zsecond_create("swrbuf", nswbuf / 2);
n = maxswzone != 0 ? maxswzone / sizeof(struct swblk) :
vm_cnt.v_page_count / 2;
swpctrie_zone = uma_zcreate("swpctrie", pctrie_node_size(), NULL, NULL,
pctrie_zone_init, NULL, UMA_ALIGN_PTR, 0);
swblk_zone = uma_zcreate("swblk", sizeof(struct swblk), NULL, NULL,
NULL, NULL, _Alignof(struct swblk) - 1, 0);
n2 = n;
do {
if (uma_zone_reserve_kva(swblk_zone, n))
break;
n -= ((n + 2) / 3);
} while (n > 0);
n = uma_zone_get_max(swblk_zone);
if (n < n2)
printf("Swap blk zone entries changed from %lu to %lu.\n",
n2, n);
swap_maxpages = n * SWAP_META_PAGES;
swzone = n * sizeof(struct swblk);
if (!uma_zone_reserve_kva(swpctrie_zone, n))
printf("Cannot reserve swap pctrie zone, "
"reduce kern.maxswzone.\n");
}
bool
swap_pager_init_object(vm_object_t object, void *handle, struct ucred *cred,
vm_ooffset_t size, vm_ooffset_t offset)
{
if (cred != NULL) {
if (!swap_reserve_by_cred(size, cred))
return (false);
crhold(cred);
}
object->un_pager.swp.writemappings = 0;
object->handle = handle;
if (cred != NULL) {
object->cred = cred;
object->charge = size;
}
return (true);
}
static vm_object_t
swap_pager_alloc_init(objtype_t otype, void *handle, struct ucred *cred,
vm_ooffset_t size, vm_ooffset_t offset)
{
vm_object_t object;
object = vm_object_allocate(otype, OFF_TO_IDX(offset +
PAGE_MASK + size));
if (!swap_pager_init_object(object, handle, cred, size, offset)) {
vm_object_deallocate(object);
return (NULL);
}
return (object);
}
static vm_object_t
swap_pager_alloc(void *handle, vm_ooffset_t size, vm_prot_t prot,
vm_ooffset_t offset, struct ucred *cred)
{
vm_object_t object;
if (handle != NULL) {
sx_xlock(&sw_alloc_sx);
object = vm_pager_object_lookup(NOBJLIST(handle), handle);
if (object == NULL) {
object = swap_pager_alloc_init(OBJT_SWAP, handle, cred,
size, offset);
if (object != NULL) {
TAILQ_INSERT_TAIL(NOBJLIST(object->handle),
object, pager_object_list);
}
}
sx_xunlock(&sw_alloc_sx);
} else {
object = swap_pager_alloc_init(OBJT_SWAP, handle, cred,
size, offset);
}
return (object);
}
static void
swap_pager_dealloc(vm_object_t object)
{
VM_OBJECT_ASSERT_WLOCKED(object);
KASSERT((object->flags & OBJ_DEAD) != 0, ("dealloc of reachable obj"));
if ((object->flags & OBJ_ANON) == 0 && object->handle != NULL) {
VM_OBJECT_WUNLOCK(object);
sx_xlock(&sw_alloc_sx);
TAILQ_REMOVE(NOBJLIST(object->handle), object,
pager_object_list);
sx_xunlock(&sw_alloc_sx);
VM_OBJECT_WLOCK(object);
}
vm_object_pip_wait(object, "swpdea");
swp_pager_meta_free_all(object);
object->handle = NULL;
object->type = OBJT_DEAD;
if (object->cred != NULL) {
swap_release_by_cred(object->charge, object->cred);
object->charge = 0;
crfree(object->cred);
object->cred = NULL;
}
vm_object_clear_flag(object, OBJ_SWAP);
}
static daddr_t
swp_pager_getswapspace(int *io_npages)
{
daddr_t blk;
struct swdevt *sp;
int mpages, npages;
KASSERT(*io_npages >= 1,
("%s: npages not positive", __func__));
blk = SWAPBLK_NONE;
mpages = *io_npages;
npages = imin(BLIST_MAX_ALLOC, mpages);
mtx_lock(&sw_dev_mtx);
sp = swdevhd;
while (!TAILQ_EMPTY(&swtailq)) {
if (sp == NULL)
sp = TAILQ_FIRST(&swtailq);
if ((sp->sw_flags & SW_CLOSING) == 0)
blk = blist_alloc(sp->sw_blist, &npages, mpages);
if (blk != SWAPBLK_NONE)
break;
sp = TAILQ_NEXT(sp, sw_list);
if (swdevhd == sp) {
if (npages == 1)
break;
mpages = npages - 1;
npages >>= 1;
}
}
if (blk != SWAPBLK_NONE) {
*io_npages = npages;
blk += sp->sw_first;
sp->sw_used += npages;
swap_pager_avail -= npages;
swp_sizecheck();
swdevhd = TAILQ_NEXT(sp, sw_list);
} else {
if (!swap_pager_full) {
printf("swp_pager_getswapspace(%d): failed\n",
*io_npages);
swap_pager_full = swap_pager_almost_full = true;
}
swdevhd = NULL;
}
mtx_unlock(&sw_dev_mtx);
return (blk);
}
static bool
swp_pager_isondev(daddr_t blk, struct swdevt *sp)
{
return (blk >= sp->sw_first && blk < sp->sw_end);
}
static void
swp_pager_strategy(struct buf *bp)
{
struct swdevt *sp;
mtx_lock(&sw_dev_mtx);
TAILQ_FOREACH(sp, &swtailq, sw_list) {
if (swp_pager_isondev(bp->b_blkno, sp)) {
mtx_unlock(&sw_dev_mtx);
if ((sp->sw_flags & SW_UNMAPPED) != 0 &&
unmapped_buf_allowed) {
bp->b_data = unmapped_buf;
bp->b_offset = 0;
} else {
pmap_qenter((vm_offset_t)bp->b_data,
&bp->b_pages[0], bp->b_bcount / PAGE_SIZE);
}
sp->sw_strategy(bp, sp);
return;
}
}
panic("Swapdev not found");
}
static void
swp_pager_freeswapspace(const struct page_range *range)
{
daddr_t blk, npages;
struct swdevt *sp;
blk = range->start;
npages = range->num;
if (npages == 0)
return;
mtx_lock(&sw_dev_mtx);
TAILQ_FOREACH(sp, &swtailq, sw_list) {
if (swp_pager_isondev(blk, sp)) {
sp->sw_used -= npages;
if ((sp->sw_flags & SW_CLOSING) == 0) {
blist_free(sp->sw_blist, blk - sp->sw_first,
npages);
swap_pager_avail += npages;
swp_sizecheck();
}
mtx_unlock(&sw_dev_mtx);
return;
}
}
panic("Swapdev not found");
}
static int
sysctl_swap_fragmentation(SYSCTL_HANDLER_ARGS)
{
struct sbuf sbuf;
struct swdevt *sp;
const char *devname;
int error;
error = sysctl_wire_old_buffer(req, 0);
if (error != 0)
return (error);
sbuf_new_for_sysctl(&sbuf, NULL, 128, req);
mtx_lock(&sw_dev_mtx);
TAILQ_FOREACH(sp, &swtailq, sw_list) {
if (vn_isdisk(sp->sw_vp))
devname = devtoname(sp->sw_vp->v_rdev);
else
devname = "[file]";
sbuf_printf(&sbuf, "\nFree space on device %s:\n", devname);
blist_stats(sp->sw_blist, &sbuf);
}
mtx_unlock(&sw_dev_mtx);
error = sbuf_finish(&sbuf);
sbuf_delete(&sbuf);
return (error);
}
void
swap_pager_freespace(vm_object_t object, vm_pindex_t start, vm_size_t size,
vm_size_t *freed)
{
MPASS((object->flags & OBJ_SWAP) != 0);
swp_pager_meta_free(object, start, size, freed);
}
static void
swap_pager_freespace_pgo(vm_object_t object, vm_pindex_t start, vm_size_t size)
{
MPASS((object->flags & OBJ_SWAP) != 0);
swp_pager_meta_free(object, start, size, NULL);
}
int
swap_pager_reserve(vm_object_t object, vm_pindex_t start, vm_pindex_t size)
{
struct pctrie_iter blks;
struct page_range range;
daddr_t addr, blk;
vm_pindex_t i, j;
int n;
swp_pager_init_freerange(&range);
VM_OBJECT_WLOCK(object);
swblk_iter_init_only(&blks, object);
for (i = 0; i < size; i += n) {
n = MIN(size - i, INT_MAX);
blk = swp_pager_getswapspace(&n);
if (blk == SWAPBLK_NONE) {
swp_pager_meta_free(object, start, i, NULL);
VM_OBJECT_WUNLOCK(object);
return (-1);
}
for (j = 0; j < n; ++j) {
addr = swp_pager_meta_build(&blks, object,
start + i + j, blk + j, false);
if (addr != SWAPBLK_NONE)
swp_pager_update_freerange(&range, addr);
}
}
swp_pager_freeswapspace(&range);
VM_OBJECT_WUNLOCK(object);
return (0);
}
void
swap_pager_copy(vm_object_t srcobject, vm_object_t dstobject,
vm_pindex_t offset, int destroysource)
{
VM_OBJECT_ASSERT_WLOCKED(srcobject);
VM_OBJECT_ASSERT_WLOCKED(dstobject);
if (destroysource && (srcobject->flags & OBJ_ANON) == 0 &&
srcobject->handle != NULL) {
VM_OBJECT_WUNLOCK(srcobject);
VM_OBJECT_WUNLOCK(dstobject);
sx_xlock(&sw_alloc_sx);
TAILQ_REMOVE(NOBJLIST(srcobject->handle), srcobject,
pager_object_list);
sx_xunlock(&sw_alloc_sx);
VM_OBJECT_WLOCK(dstobject);
VM_OBJECT_WLOCK(srcobject);
}
swp_pager_meta_transfer(srcobject, dstobject, offset, dstobject->size);
if (destroysource)
swp_pager_meta_free_all(srcobject);
}
static boolean_t
swp_pager_haspage_iter(vm_pindex_t pindex, int *before, int *after,
struct pctrie_iter *blks)
{
daddr_t blk, blk0;
int i;
blk0 = swp_pager_meta_lookup(blks, pindex);
if (blk0 == SWAPBLK_NONE) {
if (before)
*before = 0;
if (after)
*after = 0;
return (FALSE);
}
if (before != NULL) {
for (i = 1; i < SWB_NPAGES; i++) {
if (i > pindex)
break;
blk = swp_pager_meta_lookup(blks, pindex - i);
if (blk != blk0 - i)
break;
}
*before = i - 1;
}
if (after != NULL) {
for (i = 1; i < SWB_NPAGES; i++) {
blk = swp_pager_meta_lookup(blks, pindex + i);
if (blk != blk0 + i)
break;
}
*after = i - 1;
}
return (TRUE);
}
static boolean_t
swap_pager_haspage(vm_object_t object, vm_pindex_t pindex, int *before,
int *after)
{
struct pctrie_iter blks;
swblk_iter_init_only(&blks, object);
return (swp_pager_haspage_iter(pindex, before, after, &blks));
}
static void
swap_pager_unswapped_acct(vm_page_t m)
{
KASSERT((m->object->flags & OBJ_SWAP) != 0,
("Free object not swappable"));
if ((m->a.flags & PGA_SWAP_FREE) != 0)
counter_u64_add(swap_free_completed, 1);
vm_page_aflag_clear(m, PGA_SWAP_FREE | PGA_SWAP_SPACE);
}
static void
swap_pager_unswapped(vm_page_t m)
{
struct page_range range;
struct swblk *sb;
vm_object_t obj;
obj = m->object;
if (!VM_OBJECT_WOWNED(obj)) {
VM_PAGE_OBJECT_BUSY_ASSERT(m);
if ((m->a.flags & (PGA_SWAP_SPACE | PGA_SWAP_FREE)) ==
PGA_SWAP_SPACE) {
vm_page_aflag_set(m, PGA_SWAP_FREE);
counter_u64_add(swap_free_deferred, 1);
}
return;
}
swap_pager_unswapped_acct(m);
sb = swblk_lookup(m->object, m->pindex);
if (sb == NULL)
return;
range.start = sb->d[m->pindex % SWAP_META_PAGES];
if (range.start == SWAPBLK_NONE)
return;
range.num = 1;
swp_pager_freeswapspace(&range);
sb->d[m->pindex % SWAP_META_PAGES] = SWAPBLK_NONE;
swp_pager_free_empty_swblk(m->object, sb);
}
static int
swap_pager_getpages_locked(struct pctrie_iter *blks, vm_object_t object,
vm_page_t *ma, int count, int *a_rbehind, int *a_rahead, struct buf *bp)
{
vm_pindex_t pindex;
int rahead, rbehind;
VM_OBJECT_ASSERT_WLOCKED(object);
KASSERT((object->flags & OBJ_SWAP) != 0,
("%s: object not swappable", __func__));
pindex = ma[0]->pindex;
if (!swp_pager_haspage_iter(pindex, &rbehind, &rahead, blks)) {
VM_OBJECT_WUNLOCK(object);
uma_zfree(swrbuf_zone, bp);
return (VM_PAGER_FAIL);
}
KASSERT(count - 1 <= rahead,
("page count %d extends beyond swap block", count));
if ((object->flags & (OBJ_SPLIT | OBJ_DEAD)) != 0) {
rahead = count - 1;
rbehind = 0;
}
rbehind = a_rbehind != NULL ? imin(*a_rbehind, rbehind) : 0;
rahead = a_rahead != NULL ? imin(*a_rahead, rahead - count + 1) : 0;
vm_object_prepare_buf_pages(object, bp->b_pages, count, &rbehind,
&rahead, ma);
bp->b_npages = rbehind + count + rahead;
for (int i = 0; i < bp->b_npages; i++)
bp->b_pages[i]->oflags |= VPO_SWAPINPROG;
bp->b_blkno = swp_pager_meta_lookup(blks, pindex - rbehind);
KASSERT(bp->b_blkno != SWAPBLK_NONE,
("no swap blocking containing %p(%jx)", object, (uintmax_t)pindex));
vm_object_pip_add(object, bp->b_npages);
VM_OBJECT_WUNLOCK(object);
MPASS((bp->b_flags & B_MAXPHYS) != 0);
if (a_rbehind != NULL)
*a_rbehind = rbehind;
if (a_rahead != NULL)
*a_rahead = rahead;
bp->b_flags |= B_PAGING;
bp->b_iocmd = BIO_READ;
bp->b_iodone = swp_pager_async_iodone;
bp->b_rcred = crhold(thread0.td_ucred);
bp->b_wcred = crhold(thread0.td_ucred);
bp->b_bufsize = bp->b_bcount = ptoa(bp->b_npages);
bp->b_pgbefore = rbehind;
bp->b_pgafter = rahead;
VM_CNT_INC(v_swapin);
VM_CNT_ADD(v_swappgsin, bp->b_npages);
BUF_KERNPROC(bp);
swp_pager_strategy(bp);
VM_OBJECT_WLOCK(object);
while ((ma[0]->oflags & VPO_SWAPINPROG) != 0) {
ma[0]->oflags |= VPO_SWAPSLEEP;
VM_CNT_INC(v_intrans);
if (VM_OBJECT_SLEEP(object, &object->handle, PSWP,
"swread", hz * 20)) {
printf(
"swap_pager: indefinite wait buffer: bufobj: %p, blkno: %jd, size: %ld\n",
bp->b_bufobj, (intmax_t)bp->b_blkno, bp->b_bcount);
}
}
VM_OBJECT_WUNLOCK(object);
for (int i = 0; i < count; i++)
if (ma[i]->valid != VM_PAGE_BITS_ALL)
return (VM_PAGER_ERROR);
return (VM_PAGER_OK);
}
static int
swap_pager_getpages(vm_object_t object, vm_page_t *ma, int count,
int *rbehind, int *rahead)
{
struct buf *bp;
struct pctrie_iter blks;
bp = uma_zalloc(swrbuf_zone, M_WAITOK);
VM_OBJECT_WLOCK(object);
swblk_iter_init_only(&blks, object);
return (swap_pager_getpages_locked(&blks, object, ma, count, rbehind,
rahead, bp));
}
static int
swap_pager_getpages_async(vm_object_t object, vm_page_t *ma, int count,
int *rbehind, int *rahead, pgo_getpages_iodone_t iodone, void *arg)
{
int r, error;
r = swap_pager_getpages(object, ma, count, rbehind, rahead);
switch (r) {
case VM_PAGER_OK:
error = 0;
break;
case VM_PAGER_ERROR:
error = EIO;
break;
case VM_PAGER_FAIL:
error = EINVAL;
break;
default:
panic("unhandled swap_pager_getpages() error %d", r);
}
(iodone)(arg, ma, count, error);
return (r);
}
static void
swap_pager_putpages(vm_object_t object, vm_page_t *ma, int count,
int flags, int *rtvals)
{
struct pctrie_iter blks;
struct page_range range;
struct buf *bp;
daddr_t addr, blk;
vm_page_t mreq;
int i, j, n;
bool async;
KASSERT(count == 0 || ma[0]->object == object,
("%s: object mismatch %p/%p",
__func__, object, ma[0]->object));
VM_OBJECT_WUNLOCK(object);
async = curproc == pageproc && (flags & VM_PAGER_PUT_SYNC) == 0;
swp_pager_init_freerange(&range);
for (i = 0; i < count; i += n) {
n = min(count - i, nsw_cluster_max);
if (async) {
mtx_lock(&swbuf_mtx);
while (nsw_wcount_async == 0)
msleep(&nsw_wcount_async, &swbuf_mtx, PVM,
"swbufa", 0);
nsw_wcount_async--;
mtx_unlock(&swbuf_mtx);
}
blk = swp_pager_getswapspace(&n);
if (blk == SWAPBLK_NONE) {
mtx_lock(&swbuf_mtx);
if (++nsw_wcount_async == 1)
wakeup(&nsw_wcount_async);
mtx_unlock(&swbuf_mtx);
for (j = 0; j < n; ++j)
rtvals[i + j] = VM_PAGER_FAIL;
continue;
}
VM_OBJECT_WLOCK(object);
swblk_iter_init_only(&blks, object);
for (j = 0; j < n; ++j) {
mreq = ma[i + j];
vm_page_aflag_clear(mreq, PGA_SWAP_FREE);
KASSERT(mreq->object == object,
("%s: object mismatch %p/%p",
__func__, mreq->object, object));
addr = swp_pager_meta_build(&blks, object,
mreq->pindex, blk + j, false);
if (addr != SWAPBLK_NONE)
swp_pager_update_freerange(&range, addr);
MPASS(mreq->dirty == VM_PAGE_BITS_ALL);
mreq->oflags |= VPO_SWAPINPROG;
}
VM_OBJECT_WUNLOCK(object);
bp = uma_zalloc(swwbuf_zone, M_WAITOK);
MPASS((bp->b_flags & B_MAXPHYS) != 0);
if (async)
bp->b_flags |= B_ASYNC;
bp->b_flags |= B_PAGING;
bp->b_iocmd = BIO_WRITE;
bp->b_rcred = crhold(thread0.td_ucred);
bp->b_wcred = crhold(thread0.td_ucred);
bp->b_bcount = PAGE_SIZE * n;
bp->b_bufsize = PAGE_SIZE * n;
bp->b_blkno = blk;
for (j = 0; j < n; j++)
bp->b_pages[j] = ma[i + j];
bp->b_npages = n;
bp->b_dirtyoff = 0;
bp->b_dirtyend = bp->b_bcount;
VM_CNT_INC(v_swapout);
VM_CNT_ADD(v_swappgsout, bp->b_npages);
for (j = 0; j < n; j++)
rtvals[i + j] = VM_PAGER_PEND;
if (async) {
bp->b_iodone = swp_pager_async_iodone;
BUF_KERNPROC(bp);
swp_pager_strategy(bp);
continue;
}
bp->b_iodone = bdone;
swp_pager_strategy(bp);
bwait(bp, PVM, "swwrt");
swp_pager_async_iodone(bp);
}
swp_pager_freeswapspace(&range);
VM_OBJECT_WLOCK(object);
}
static void
swp_pager_async_iodone(struct buf *bp)
{
int i;
vm_object_t object = NULL;
if (bp->b_ioflags & BIO_ERROR && bp->b_error != ENOMEM) {
printf(
"swap_pager: I/O error - %s failed; blkno %ld,"
"size %ld, error %d\n",
((bp->b_iocmd == BIO_READ) ? "pagein" : "pageout"),
(long)bp->b_blkno,
(long)bp->b_bcount,
bp->b_error
);
}
if (buf_mapped(bp))
pmap_qremove((vm_offset_t)bp->b_data, bp->b_npages);
else
bp->b_data = bp->b_kvabase;
if (bp->b_npages) {
object = bp->b_pages[0]->object;
VM_OBJECT_WLOCK(object);
}
for (i = 0; i < bp->b_npages; ++i) {
vm_page_t m = bp->b_pages[i];
m->oflags &= ~VPO_SWAPINPROG;
if (m->oflags & VPO_SWAPSLEEP) {
m->oflags &= ~VPO_SWAPSLEEP;
wakeup(&object->handle);
}
vm_page_aflag_set(m, PGA_SWAP_SPACE);
if (bp->b_ioflags & BIO_ERROR) {
if (bp->b_iocmd == BIO_READ) {
vm_page_invalid(m);
if (i < bp->b_pgbefore ||
i >= bp->b_npages - bp->b_pgafter)
vm_page_free_invalid(m);
} else {
MPASS(m->dirty == VM_PAGE_BITS_ALL);
vm_page_activate(m);
vm_page_sunbusy(m);
}
} else if (bp->b_iocmd == BIO_READ) {
KASSERT(!pmap_page_is_mapped(m),
("swp_pager_async_iodone: page %p is mapped", m));
KASSERT(m->dirty == 0,
("swp_pager_async_iodone: page %p is dirty", m));
vm_page_valid(m);
if (i < bp->b_pgbefore ||
i >= bp->b_npages - bp->b_pgafter)
vm_page_readahead_finish(m);
} else {
KASSERT(!pmap_page_is_write_mapped(m),
("swp_pager_async_iodone: page %p is not write"
" protected", m));
vm_page_undirty(m);
vm_page_deactivate_noreuse(m);
vm_page_sunbusy(m);
}
}
if (object != NULL) {
vm_object_pip_wakeupn(object, bp->b_npages);
VM_OBJECT_WUNLOCK(object);
}
if (bp->b_vp) {
bp->b_vp = NULL;
bp->b_bufobj = NULL;
}
if (bp->b_flags & B_ASYNC) {
mtx_lock(&swbuf_mtx);
if (++nsw_wcount_async == 1)
wakeup(&nsw_wcount_async);
mtx_unlock(&swbuf_mtx);
}
uma_zfree((bp->b_iocmd == BIO_READ) ? swrbuf_zone : swwbuf_zone, bp);
}
int
swap_pager_nswapdev(void)
{
return (nswapdev);
}
static void
swp_pager_force_dirty(struct page_range *range, vm_page_t m, daddr_t *blk)
{
vm_page_dirty(m);
swap_pager_unswapped_acct(m);
swp_pager_update_freerange(range, *blk);
*blk = SWAPBLK_NONE;
vm_page_launder(m);
}
u_long
swap_pager_swapped_pages(vm_object_t object)
{
struct pctrie_iter blks;
struct swblk *sb;
u_long res;
int i;
VM_OBJECT_ASSERT_LOCKED(object);
if (swblk_is_empty(object))
return (0);
res = 0;
for (sb = swblk_iter_init(&blks, object, 0); sb != NULL;
sb = swblk_iter_next(&blks)) {
for (i = 0; i < SWAP_META_PAGES; i++) {
if (sb->d[i] != SWAPBLK_NONE)
res++;
}
}
return (res);
}
static void
swap_pager_swapoff_object(struct swdevt *sp, vm_object_t object,
struct buf **bp)
{
struct pctrie_iter blks, pages;
struct page_range range;
struct swblk *sb;
vm_page_t m;
int i, rahead, rv;
bool sb_empty;
VM_OBJECT_ASSERT_WLOCKED(object);
KASSERT((object->flags & OBJ_SWAP) != 0,
("%s: Object not swappable", __func__));
KASSERT((object->flags & OBJ_DEAD) == 0,
("%s: Object already dead", __func__));
KASSERT((sp->sw_flags & SW_CLOSING) != 0,
("%s: Device not blocking further allocations", __func__));
vm_page_iter_init(&pages, object);
swp_pager_init_freerange(&range);
sb = swblk_iter_init(&blks, object, 0);
while (sb != NULL) {
sb_empty = true;
for (i = 0; i < SWAP_META_PAGES; i++) {
if (sb->d[i] == SWAPBLK_NONE)
continue;
if (!swp_pager_isondev(sb->d[i], sp)) {
sb_empty = false;
continue;
}
m = vm_radix_iter_lookup(&pages, blks.index + i);
if (m != NULL && (m->oflags & VPO_SWAPINPROG) != 0) {
m->oflags |= VPO_SWAPSLEEP;
VM_OBJECT_SLEEP(object, &object->handle, PSWP,
"swpoff", 0);
break;
}
if (m != NULL && vm_page_all_valid(m)) {
swp_pager_force_dirty(&range, m, &sb->d[i]);
continue;
}
if (m != NULL) {
if (!vm_page_busy_acquire(m, VM_ALLOC_WAITFAIL))
break;
} else {
m = vm_page_alloc_iter(object, blks.index + i,
VM_ALLOC_NORMAL | VM_ALLOC_WAITFAIL,
&pages);
if (m == NULL)
break;
}
vm_object_pip_add(object, 1);
rahead = SWAP_META_PAGES;
rv = swap_pager_getpages_locked(&blks, object, &m, 1,
NULL, &rahead, *bp);
if (rv != VM_PAGER_OK)
panic("%s: read from swap failed: %d",
__func__, rv);
*bp = uma_zalloc(swrbuf_zone, M_WAITOK);
VM_OBJECT_WLOCK(object);
vm_object_pip_wakeupn(object, 1);
KASSERT(vm_page_all_valid(m),
("%s: Page %p not all valid", __func__, m));
vm_page_deactivate_noreuse(m);
vm_page_xunbusy(m);
break;
}
if (i < SWAP_META_PAGES) {
if ((object->flags & OBJ_DEAD) != 0) {
vm_object_pip_wait(object, "swpoff");
swp_pager_meta_free_all(object);
break;
}
pctrie_iter_reset(&pages);
sb = swblk_iter_init(&blks, object, blks.index);
continue;
}
if (sb_empty) {
swblk_iter_remove(&blks);
uma_zfree(swblk_zone, sb);
}
sb = swblk_iter_next(&blks);
}
swp_pager_freeswapspace(&range);
}
static void
swap_pager_swapoff(struct swdevt *sp)
{
vm_object_t object;
struct buf *bp;
int retries;
sx_assert(&swdev_syscall_lock, SA_XLOCKED);
retries = 0;
full_rescan:
bp = uma_zalloc(swrbuf_zone, M_WAITOK);
mtx_lock(&vm_object_list_mtx);
TAILQ_FOREACH(object, &vm_object_list, object_list) {
if ((object->flags & OBJ_SWAP) == 0)
continue;
mtx_unlock(&vm_object_list_mtx);
VM_OBJECT_WLOCK(object);
if ((object->flags & OBJ_DEAD) != 0)
goto next_obj;
atomic_thread_fence_acq();
if ((object->flags & OBJ_SWAP) == 0)
goto next_obj;
swap_pager_swapoff_object(sp, object, &bp);
next_obj:
VM_OBJECT_WUNLOCK(object);
mtx_lock(&vm_object_list_mtx);
}
mtx_unlock(&vm_object_list_mtx);
uma_zfree(swrbuf_zone, bp);
if (sp->sw_used) {
retries++;
if (retries > 100) {
panic("swapoff: failed to locate %d swap blocks",
sp->sw_used);
}
pause("swpoff", hz / 20);
goto full_rescan;
}
EVENTHANDLER_INVOKE(swapoff, sp);
}
static bool
swp_pager_swblk_empty(struct swblk *sb, int start, int limit)
{
int i;
MPASS(0 <= start && start <= limit && limit <= SWAP_META_PAGES);
for (i = start; i < limit; i++) {
if (sb->d[i] != SWAPBLK_NONE)
return (false);
}
return (true);
}
static void
swp_pager_free_empty_swblk(vm_object_t object, struct swblk *sb)
{
if (swp_pager_swblk_empty(sb, 0, SWAP_META_PAGES)) {
swblk_lookup_remove(object, sb);
uma_zfree(swblk_zone, sb);
}
}
static daddr_t
swp_pager_meta_build(struct pctrie_iter *blks, vm_object_t object,
vm_pindex_t pindex, daddr_t swapblk, bool nowait_noreplace)
{
static volatile int swblk_zone_exhausted, swpctrie_zone_exhausted;
struct swblk *sb, *sb1;
vm_pindex_t modpi;
daddr_t prev_swapblk;
int error, i;
VM_OBJECT_ASSERT_WLOCKED(object);
sb = swblk_iter_lookup(blks, pindex);
if (sb == NULL) {
if (swapblk == SWAPBLK_NONE)
return (SWAPBLK_NONE);
for (;;) {
sb = uma_zalloc(swblk_zone, M_NOWAIT | (curproc ==
pageproc ? M_USE_RESERVE : 0));
if (sb != NULL) {
sb->p = rounddown(pindex, SWAP_META_PAGES);
for (i = 0; i < SWAP_META_PAGES; i++)
sb->d[i] = SWAPBLK_NONE;
if (atomic_cmpset_int(&swblk_zone_exhausted,
1, 0))
printf("swblk zone ok\n");
break;
}
if (nowait_noreplace)
return (swapblk);
VM_OBJECT_WUNLOCK(object);
if (uma_zone_exhausted(swblk_zone)) {
if (atomic_cmpset_int(&swblk_zone_exhausted,
0, 1))
printf("swap blk zone exhausted, "
"increase kern.maxswzone\n");
vm_pageout_oom(VM_OOM_SWAPZ);
pause("swzonxb", 10);
} else
uma_zwait(swblk_zone);
VM_OBJECT_WLOCK(object);
sb = swblk_iter_reinit(blks, object, pindex);
if (sb != NULL)
goto allocated;
}
for (;;) {
error = swblk_iter_insert(blks, sb);
if (error == 0) {
if (atomic_cmpset_int(&swpctrie_zone_exhausted,
1, 0))
printf("swpctrie zone ok\n");
break;
}
if (nowait_noreplace) {
uma_zfree(swblk_zone, sb);
return (swapblk);
}
VM_OBJECT_WUNLOCK(object);
if (uma_zone_exhausted(swpctrie_zone)) {
if (atomic_cmpset_int(&swpctrie_zone_exhausted,
0, 1))
printf("swap pctrie zone exhausted, "
"increase kern.maxswzone\n");
vm_pageout_oom(VM_OOM_SWAPZ);
pause("swzonxp", 10);
} else
uma_zwait(swpctrie_zone);
VM_OBJECT_WLOCK(object);
sb1 = swblk_iter_reinit(blks, object, pindex);
if (sb1 != NULL) {
uma_zfree(swblk_zone, sb);
sb = sb1;
goto allocated;
}
}
}
allocated:
MPASS(sb->p == rounddown(pindex, SWAP_META_PAGES));
modpi = pindex % SWAP_META_PAGES;
prev_swapblk = sb->d[modpi];
if (!nowait_noreplace || prev_swapblk == SWAPBLK_NONE) {
sb->d[modpi] = swapblk;
if (swapblk == SWAPBLK_NONE &&
swp_pager_swblk_empty(sb, 0, SWAP_META_PAGES)) {
swblk_iter_remove(blks);
uma_zfree(swblk_zone, sb);
}
}
return (prev_swapblk);
}
static void
swp_pager_meta_transfer(vm_object_t srcobject, vm_object_t dstobject,
vm_pindex_t pindex, vm_pindex_t count)
{
struct pctrie_iter dstblks, srcblks;
struct page_range range;
struct swblk *sb;
daddr_t blk, d[SWAP_META_PAGES];
vm_pindex_t last;
int d_mask, i, limit, start;
_Static_assert(8 * sizeof(d_mask) >= SWAP_META_PAGES,
"d_mask not big enough");
VM_OBJECT_ASSERT_WLOCKED(srcobject);
VM_OBJECT_ASSERT_WLOCKED(dstobject);
if (count == 0 || swblk_is_empty(srcobject))
return;
swp_pager_init_freerange(&range);
d_mask = 0;
last = pindex + count;
swblk_iter_init_only(&dstblks, dstobject);
for (sb = swblk_iter_limit_init(&srcblks, srcobject, pindex, last),
start = swblk_start(sb, pindex);
sb != NULL; sb = swblk_iter_next(&srcblks), start = 0) {
limit = MIN(last - srcblks.index, SWAP_META_PAGES);
for (i = start; i < limit; i++) {
if (sb->d[i] == SWAPBLK_NONE)
continue;
blk = swp_pager_meta_build(&dstblks, dstobject,
srcblks.index + i - pindex, sb->d[i], true);
if (blk == sb->d[i]) {
d[i] = blk;
d_mask |= 1 << i;
} else if (blk != SWAPBLK_NONE) {
swp_pager_update_freerange(&range, sb->d[i]);
}
sb->d[i] = SWAPBLK_NONE;
}
if (swp_pager_swblk_empty(sb, 0, start) &&
swp_pager_swblk_empty(sb, limit, SWAP_META_PAGES)) {
swblk_iter_remove(&srcblks);
uma_zfree(swblk_zone, sb);
}
if (d_mask != 0) {
VM_OBJECT_WUNLOCK(srcobject);
do {
i = ffs(d_mask) - 1;
swp_pager_meta_build(&dstblks, dstobject,
srcblks.index + i - pindex, d[i], false);
d_mask &= ~(1 << i);
} while (d_mask != 0);
VM_OBJECT_WLOCK(srcobject);
pctrie_iter_reset(&srcblks);
}
}
swp_pager_freeswapspace(&range);
}
static void
swp_pager_meta_free(vm_object_t object, vm_pindex_t pindex, vm_pindex_t count,
vm_size_t *freed)
{
struct pctrie_iter blks, pages;
struct page_range range;
struct swblk *sb;
vm_page_t m;
vm_pindex_t last;
vm_size_t fc;
int i, limit, start;
VM_OBJECT_ASSERT_WLOCKED(object);
fc = 0;
if (count == 0 || swblk_is_empty(object))
goto out;
swp_pager_init_freerange(&range);
vm_page_iter_init(&pages, object);
last = pindex + count;
for (sb = swblk_iter_limit_init(&blks, object, pindex, last),
start = swblk_start(sb, pindex);
sb != NULL; sb = swblk_iter_next(&blks), start = 0) {
limit = MIN(last - blks.index, SWAP_META_PAGES);
for (i = start; i < limit; i++) {
if (sb->d[i] == SWAPBLK_NONE)
continue;
swp_pager_update_freerange(&range, sb->d[i]);
if (freed != NULL) {
m = vm_radix_iter_lookup(&pages, blks.index + i);
if (m == NULL || vm_page_none_valid(m))
fc++;
}
sb->d[i] = SWAPBLK_NONE;
}
if (swp_pager_swblk_empty(sb, 0, start) &&
swp_pager_swblk_empty(sb, limit, SWAP_META_PAGES)) {
swblk_iter_remove(&blks);
uma_zfree(swblk_zone, sb);
}
}
swp_pager_freeswapspace(&range);
out:
if (freed != NULL)
*freed = fc;
}
static void
swp_pager_meta_free_block(struct swblk *sb, void *rangev)
{
struct page_range *range = rangev;
for (int i = 0; i < SWAP_META_PAGES; i++) {
if (sb->d[i] != SWAPBLK_NONE)
swp_pager_update_freerange(range, sb->d[i]);
}
uma_zfree(swblk_zone, sb);
}
static void
swp_pager_meta_free_all(vm_object_t object)
{
struct page_range range;
VM_OBJECT_ASSERT_WLOCKED(object);
swp_pager_init_freerange(&range);
SWAP_PCTRIE_RECLAIM_CALLBACK(&object->un_pager.swp.swp_blks,
swp_pager_meta_free_block, &range);
swp_pager_freeswapspace(&range);
}
static daddr_t
swp_pager_meta_lookup(struct pctrie_iter *blks, vm_pindex_t pindex)
{
struct swblk *sb;
sb = swblk_iter_lookup(blks, pindex);
if (sb == NULL)
return (SWAPBLK_NONE);
return (sb->d[pindex % SWAP_META_PAGES]);
}
static vm_pindex_t
swap_pager_iter_find_least(struct pctrie_iter *blks, vm_pindex_t pindex)
{
struct swblk *sb;
int i;
if ((sb = swblk_iter_lookup_ge(blks, pindex)) == NULL)
return (OBJ_MAX_SIZE);
if (blks->index < pindex) {
for (i = pindex % SWAP_META_PAGES; i < SWAP_META_PAGES; i++) {
if (sb->d[i] != SWAPBLK_NONE)
return (blks->index + i);
}
if ((sb = swblk_iter_next(blks)) == NULL)
return (OBJ_MAX_SIZE);
}
for (i = 0; i < SWAP_META_PAGES; i++) {
if (sb->d[i] != SWAPBLK_NONE)
return (blks->index + i);
}
MPASS(0);
return (OBJ_MAX_SIZE);
}
vm_pindex_t
swap_pager_seek_data(vm_object_t object, vm_pindex_t pindex)
{
struct pctrie_iter blks, pages;
vm_page_t m;
vm_pindex_t swap_index;
VM_OBJECT_ASSERT_RLOCKED(object);
vm_page_iter_init(&pages, object);
m = vm_radix_iter_lookup_ge(&pages, pindex);
if (m != NULL && pages.index == pindex && vm_page_any_valid(m))
return (pages.index);
swblk_iter_init_only(&blks, object);
swap_index = swap_pager_iter_find_least(&blks, pindex);
if (swap_index == pindex)
return (swap_index);
while (m != NULL && pages.index < swap_index) {
if (vm_page_any_valid(m))
return (pages.index);
m = vm_radix_iter_step(&pages);
}
if (swap_index == OBJ_MAX_SIZE)
swap_index = object->size;
return (swap_index);
}
vm_pindex_t
swap_pager_seek_hole(vm_object_t object, vm_pindex_t pindex)
{
struct pctrie_iter blks, pages;
struct swblk *sb;
vm_page_t m;
VM_OBJECT_ASSERT_RLOCKED(object);
vm_page_iter_init(&pages, object);
swblk_iter_init_only(&blks, object);
while (((m = vm_radix_iter_lookup(&pages, pindex)) != NULL &&
vm_page_any_valid(m)) ||
((sb = swblk_iter_lookup(&blks, pindex)) != NULL &&
sb->d[pindex % SWAP_META_PAGES] != SWAPBLK_NONE))
pindex++;
return (pindex);
}
bool
swap_pager_scan_all_shadowed(vm_object_t object)
{
struct pctrie_iter backing_blks, backing_pages, blks, pages;
vm_object_t backing_object;
vm_page_t p, pp;
vm_pindex_t backing_offset_index, new_pindex, pi, pi_ubound, ps, pv;
VM_OBJECT_ASSERT_WLOCKED(object);
VM_OBJECT_ASSERT_WLOCKED(object->backing_object);
backing_object = object->backing_object;
if ((backing_object->flags & OBJ_ANON) == 0)
return (false);
KASSERT((object->flags & OBJ_ANON) != 0,
("Shadow object is not anonymous"));
backing_offset_index = OFF_TO_IDX(object->backing_object_offset);
pi_ubound = MIN(backing_object->size,
backing_offset_index + object->size);
vm_page_iter_init(&pages, object);
vm_page_iter_init(&backing_pages, backing_object);
swblk_iter_init_only(&blks, object);
swblk_iter_init_only(&backing_blks, backing_object);
pv = ps = pi = backing_offset_index - 1;
for (;;) {
if (pi == pv) {
p = vm_radix_iter_lookup_ge(&backing_pages, pv + 1);
pv = p != NULL ? p->pindex : backing_object->size;
}
if (pi == ps)
ps = swap_pager_iter_find_least(&backing_blks, ps + 1);
pi = MIN(pv, ps);
if (pi >= pi_ubound)
break;
if (pi == pv) {
if (vm_page_tryxbusy(p) == 0)
return (false);
if (!vm_page_all_valid(p))
break;
}
new_pindex = pi - backing_offset_index;
pp = vm_radix_iter_lookup(&pages, new_pindex);
if ((pp == NULL || vm_page_none_valid(pp)) &&
!swp_pager_haspage_iter(new_pindex, NULL, NULL, &blks))
break;
if (pi == pv)
vm_page_xunbusy(p);
}
if (pi < pi_ubound) {
if (pi == pv)
vm_page_xunbusy(p);
return (false);
}
return (true);
}
#ifndef _SYS_SYSPROTO_H_
struct swapon_args {
char *name;
};
#endif
int
sys_swapon(struct thread *td, struct swapon_args *uap)
{
struct vattr attr;
struct vnode *vp;
struct nameidata nd;
int error;
error = priv_check(td, PRIV_SWAPON);
if (error)
return (error);
sx_xlock(&swdev_syscall_lock);
if (swblk_zone == NULL) {
error = ENOMEM;
goto done;
}
NDINIT(&nd, LOOKUP, ISOPEN | FOLLOW | LOCKLEAF | AUDITVNODE1,
UIO_USERSPACE, uap->name);
error = namei(&nd);
if (error)
goto done;
NDFREE_PNBUF(&nd);
vp = nd.ni_vp;
if (vn_isdisk_error(vp, &error)) {
error = swapongeom(vp);
} else if (vp->v_type == VREG &&
(vp->v_mount->mnt_vfc->vfc_flags & VFCF_NETWORK) != 0 &&
(error = VOP_GETATTR(vp, &attr, td->td_ucred)) == 0) {
error = swaponvp(td, vp, attr.va_size / DEV_BSIZE);
}
if (error != 0)
vput(vp);
else
VOP_UNLOCK(vp);
done:
sx_xunlock(&swdev_syscall_lock);
return (error);
}
static void
swapon_check_swzone(void)
{
if (swap_total > swap_maxpages / 2) {
printf("warning: total configured swap (%lu pages) "
"exceeds maximum recommended amount (%lu pages).\n",
swap_total, swap_maxpages / 2);
printf("warning: increase kern.maxswzone "
"or reduce amount of swap.\n");
}
}
static int
swaponsomething(struct vnode *vp, void *id, u_long nblks,
sw_strategy_t *strategy, sw_close_t *close, dev_t dev, int flags)
{
struct swdevt *sp, *tsp;
daddr_t dvbase;
nblks &= ~(ctodb(1) - 1);
nblks = dbtoc(nblks);
if (nblks == 0)
return (EXTERROR(EINVAL, "swap device too small"));
sp = malloc(sizeof *sp, M_VMPGDATA, M_WAITOK | M_ZERO);
sp->sw_blist = blist_create(nblks, M_WAITOK);
sp->sw_vp = vp;
sp->sw_id = id;
sp->sw_dev = dev;
sp->sw_nblks = nblks;
sp->sw_used = 0;
sp->sw_strategy = strategy;
sp->sw_close = close;
sp->sw_flags = flags;
blist_free(sp->sw_blist, howmany(BBSIZE, PAGE_SIZE),
nblks - howmany(BBSIZE, PAGE_SIZE));
dvbase = 0;
mtx_lock(&sw_dev_mtx);
TAILQ_FOREACH(tsp, &swtailq, sw_list) {
if (tsp->sw_end >= dvbase) {
dvbase = tsp->sw_end + 1;
}
}
sp->sw_first = dvbase;
sp->sw_end = dvbase + nblks;
TAILQ_INSERT_TAIL(&swtailq, sp, sw_list);
nswapdev++;
swap_pager_avail += nblks - howmany(BBSIZE, PAGE_SIZE);
swap_total += nblks;
swapon_check_swzone();
swp_sizecheck();
mtx_unlock(&sw_dev_mtx);
EVENTHANDLER_INVOKE(swapon, sp);
return (0);
}
static int
kern_swapoff(struct thread *td, const char *name, enum uio_seg name_seg,
u_int flags)
{
struct vnode *vp;
struct nameidata nd;
struct swdevt *sp;
int error;
error = priv_check(td, PRIV_SWAPOFF);
if (error != 0)
return (error);
if ((flags & ~(SWAPOFF_FORCE)) != 0)
return (EINVAL);
sx_xlock(&swdev_syscall_lock);
NDINIT(&nd, LOOKUP, FOLLOW | AUDITVNODE1, name_seg, name);
error = namei(&nd);
if (error)
goto done;
NDFREE_PNBUF(&nd);
vp = nd.ni_vp;
mtx_lock(&sw_dev_mtx);
TAILQ_FOREACH(sp, &swtailq, sw_list) {
if (sp->sw_vp == vp)
break;
}
mtx_unlock(&sw_dev_mtx);
if (sp == NULL) {
error = EINVAL;
goto done;
}
error = swapoff_one(sp, td->td_ucred, flags);
done:
sx_xunlock(&swdev_syscall_lock);
return (error);
}
#ifdef COMPAT_FREEBSD13
int
freebsd13_swapoff(struct thread *td, struct freebsd13_swapoff_args *uap)
{
return (kern_swapoff(td, uap->name, UIO_USERSPACE, 0));
}
#endif
int
sys_swapoff(struct thread *td, struct swapoff_args *uap)
{
return (kern_swapoff(td, uap->name, UIO_USERSPACE, uap->flags));
}
static int
swapoff_one(struct swdevt *sp, struct ucred *cred, u_int flags)
{
u_long nblks;
#ifdef MAC
int error;
#endif
sx_assert(&swdev_syscall_lock, SA_XLOCKED);
#ifdef MAC
(void) vn_lock(sp->sw_vp, LK_EXCLUSIVE | LK_RETRY);
error = mac_system_check_swapoff(cred, sp->sw_vp);
(void) VOP_UNLOCK(sp->sw_vp);
if (error != 0)
return (error);
#endif
nblks = sp->sw_nblks;
if ((flags & SWAPOFF_FORCE) == 0 &&
vm_free_count() + swap_pager_avail < nblks + nswap_lowat)
return (ENOMEM);
mtx_lock(&sw_dev_mtx);
sp->sw_flags |= SW_CLOSING;
swap_pager_avail -= blist_fill(sp->sw_blist, 0, nblks);
swap_total -= nblks;
mtx_unlock(&sw_dev_mtx);
swap_pager_swapoff(sp);
sp->sw_close(curthread, sp);
mtx_lock(&sw_dev_mtx);
sp->sw_id = NULL;
TAILQ_REMOVE(&swtailq, sp, sw_list);
nswapdev--;
if (nswapdev == 0)
swap_pager_full = swap_pager_almost_full = true;
if (swdevhd == sp)
swdevhd = NULL;
mtx_unlock(&sw_dev_mtx);
blist_destroy(sp->sw_blist);
free(sp, M_VMPGDATA);
return (0);
}
void
swapoff_all(void)
{
struct swdevt *sp, *spt;
const char *devname;
int error;
sx_xlock(&swdev_syscall_lock);
mtx_lock(&sw_dev_mtx);
TAILQ_FOREACH_SAFE(sp, &swtailq, sw_list, spt) {
mtx_unlock(&sw_dev_mtx);
if (vn_isdisk(sp->sw_vp))
devname = devtoname(sp->sw_vp->v_rdev);
else
devname = "[file]";
error = swapoff_one(sp, thread0.td_ucred, SWAPOFF_FORCE);
if (error != 0) {
printf("Cannot remove swap device %s (error=%d), "
"skipping.\n", devname, error);
} else if (bootverbose) {
printf("Swap device %s removed.\n", devname);
}
mtx_lock(&sw_dev_mtx);
}
mtx_unlock(&sw_dev_mtx);
sx_xunlock(&swdev_syscall_lock);
}
void
swap_pager_status(int *total, int *used)
{
*total = swap_total;
*used = swap_total - swap_pager_avail -
nswapdev * howmany(BBSIZE, PAGE_SIZE);
}
int
swap_dev_info(int name, struct xswdev *xs, char *devname, size_t len)
{
struct swdevt *sp;
const char *tmp_devname;
int error, n;
n = 0;
error = ENOENT;
mtx_lock(&sw_dev_mtx);
TAILQ_FOREACH(sp, &swtailq, sw_list) {
if (n != name) {
n++;
continue;
}
xs->xsw_version = XSWDEV_VERSION;
xs->xsw_dev = sp->sw_dev;
xs->xsw_flags = sp->sw_flags;
xs->xsw_nblks = sp->sw_nblks;
xs->xsw_used = sp->sw_used;
if (devname != NULL) {
if (vn_isdisk(sp->sw_vp))
tmp_devname = devtoname(sp->sw_vp->v_rdev);
else
tmp_devname = "[file]";
strncpy(devname, tmp_devname, len);
}
error = 0;
break;
}
mtx_unlock(&sw_dev_mtx);
return (error);
}
#if defined(COMPAT_FREEBSD11)
#define XSWDEV_VERSION_11 1
struct xswdev11 {
u_int xsw_version;
uint32_t xsw_dev;
int xsw_flags;
int xsw_nblks;
int xsw_used;
};
#endif
#if defined(__amd64__) && defined(COMPAT_FREEBSD32)
struct xswdev32 {
u_int xsw_version;
u_int xsw_dev1, xsw_dev2;
int xsw_flags;
int xsw_nblks;
int xsw_used;
};
#endif
static int
sysctl_vm_swap_info(SYSCTL_HANDLER_ARGS)
{
struct xswdev xs;
#if defined(__amd64__) && defined(COMPAT_FREEBSD32)
struct xswdev32 xs32;
#endif
#if defined(COMPAT_FREEBSD11)
struct xswdev11 xs11;
#endif
int error;
if (arg2 != 1)
return (EINVAL);
memset(&xs, 0, sizeof(xs));
error = swap_dev_info(*(int *)arg1, &xs, NULL, 0);
if (error != 0)
return (error);
#if defined(__amd64__) && defined(COMPAT_FREEBSD32)
if (req->oldlen == sizeof(xs32)) {
memset(&xs32, 0, sizeof(xs32));
xs32.xsw_version = XSWDEV_VERSION;
xs32.xsw_dev1 = xs.xsw_dev;
xs32.xsw_dev2 = xs.xsw_dev >> 32;
xs32.xsw_flags = xs.xsw_flags;
xs32.xsw_nblks = xs.xsw_nblks;
xs32.xsw_used = xs.xsw_used;
error = SYSCTL_OUT(req, &xs32, sizeof(xs32));
return (error);
}
#endif
#if defined(COMPAT_FREEBSD11)
if (req->oldlen == sizeof(xs11)) {
memset(&xs11, 0, sizeof(xs11));
xs11.xsw_version = XSWDEV_VERSION_11;
xs11.xsw_dev = xs.xsw_dev;
xs11.xsw_flags = xs.xsw_flags;
xs11.xsw_nblks = xs.xsw_nblks;
xs11.xsw_used = xs.xsw_used;
error = SYSCTL_OUT(req, &xs11, sizeof(xs11));
return (error);
}
#endif
error = SYSCTL_OUT(req, &xs, sizeof(xs));
return (error);
}
SYSCTL_INT(_vm, OID_AUTO, nswapdev, CTLFLAG_RD, &nswapdev, 0,
"Number of swap devices");
SYSCTL_NODE(_vm, OID_AUTO, swap_info, CTLFLAG_RD | CTLFLAG_MPSAFE,
sysctl_vm_swap_info,
"Swap statistics by device");
long
vmspace_swap_count(struct vmspace *vmspace)
{
struct pctrie_iter blks;
vm_map_t map;
vm_map_entry_t cur;
vm_object_t object;
struct swblk *sb;
vm_pindex_t e, pi;
long count;
int i, limit, start;
map = &vmspace->vm_map;
count = 0;
VM_MAP_ENTRY_FOREACH(cur, map) {
if ((cur->eflags & MAP_ENTRY_IS_SUB_MAP) != 0)
continue;
object = cur->object.vm_object;
if (object == NULL || (object->flags & OBJ_SWAP) == 0)
continue;
VM_OBJECT_RLOCK(object);
if ((object->flags & OBJ_SWAP) == 0)
goto unlock;
pi = OFF_TO_IDX(cur->offset);
e = pi + OFF_TO_IDX(cur->end - cur->start);
for (sb = swblk_iter_limit_init(&blks, object, pi, e),
start = swblk_start(sb, pi);
sb != NULL; sb = swblk_iter_next(&blks), start = 0) {
limit = MIN(e - blks.index, SWAP_META_PAGES);
for (i = start; i < limit; i++) {
if (sb->d[i] != SWAPBLK_NONE)
count++;
}
}
unlock:
VM_OBJECT_RUNLOCK(object);
}
return (count);
}
static g_orphan_t swapgeom_orphan;
static struct g_class g_swap_class = {
.name = "SWAP",
.version = G_VERSION,
.orphan = swapgeom_orphan,
};
DECLARE_GEOM_CLASS(g_swap_class, g_class);
static void
swapgeom_close_ev(void *arg, int flags)
{
struct g_consumer *cp;
cp = arg;
g_access(cp, -1, -1, 0);
g_detach(cp);
g_destroy_consumer(cp);
}
static void
swapgeom_acquire(struct g_consumer *cp)
{
mtx_assert(&sw_dev_mtx, MA_OWNED);
cp->index++;
}
static void
swapgeom_release(struct g_consumer *cp, struct swdevt *sp)
{
mtx_assert(&sw_dev_mtx, MA_OWNED);
cp->index--;
if (cp->index == 0) {
if (g_post_event(swapgeom_close_ev, cp, M_NOWAIT, NULL) == 0)
sp->sw_id = NULL;
}
}
static void
swapgeom_done(struct bio *bp2)
{
struct swdevt *sp;
struct buf *bp;
struct g_consumer *cp;
bp = bp2->bio_caller2;
cp = bp2->bio_from;
bp->b_ioflags = bp2->bio_flags;
if (bp2->bio_error)
bp->b_ioflags |= BIO_ERROR;
bp->b_resid = bp->b_bcount - bp2->bio_completed;
bp->b_error = bp2->bio_error;
bp->b_caller1 = NULL;
bufdone(bp);
sp = bp2->bio_caller1;
mtx_lock(&sw_dev_mtx);
swapgeom_release(cp, sp);
mtx_unlock(&sw_dev_mtx);
g_destroy_bio(bp2);
}
static void
swapgeom_strategy(struct buf *bp, struct swdevt *sp)
{
struct bio *bio;
struct g_consumer *cp;
mtx_lock(&sw_dev_mtx);
cp = sp->sw_id;
if (cp == NULL) {
mtx_unlock(&sw_dev_mtx);
bp->b_error = ENXIO;
bp->b_ioflags |= BIO_ERROR;
bufdone(bp);
return;
}
swapgeom_acquire(cp);
mtx_unlock(&sw_dev_mtx);
if (bp->b_iocmd == BIO_WRITE)
bio = g_new_bio();
else
bio = g_alloc_bio();
if (bio == NULL) {
mtx_lock(&sw_dev_mtx);
swapgeom_release(cp, sp);
mtx_unlock(&sw_dev_mtx);
bp->b_error = ENOMEM;
bp->b_ioflags |= BIO_ERROR;
printf("swap_pager: cannot allocate bio\n");
bufdone(bp);
return;
}
bp->b_caller1 = bio;
bio->bio_caller1 = sp;
bio->bio_caller2 = bp;
bio->bio_cmd = bp->b_iocmd;
bio->bio_offset = (bp->b_blkno - sp->sw_first) * PAGE_SIZE;
bio->bio_length = bp->b_bcount;
bio->bio_done = swapgeom_done;
bio->bio_flags |= BIO_SWAP;
if (!buf_mapped(bp)) {
bio->bio_ma = bp->b_pages;
bio->bio_data = unmapped_buf;
bio->bio_ma_offset = (vm_offset_t)bp->b_offset & PAGE_MASK;
bio->bio_ma_n = bp->b_npages;
bio->bio_flags |= BIO_UNMAPPED;
} else {
bio->bio_data = bp->b_data;
bio->bio_ma = NULL;
}
g_io_request(bio, cp);
return;
}
static void
swapgeom_orphan(struct g_consumer *cp)
{
struct swdevt *sp;
int destroy;
mtx_lock(&sw_dev_mtx);
TAILQ_FOREACH(sp, &swtailq, sw_list) {
if (sp->sw_id == cp) {
sp->sw_flags |= SW_CLOSING;
break;
}
}
cp->index--;
destroy = ((sp != NULL) && (cp->index == 0));
if (destroy)
sp->sw_id = NULL;
mtx_unlock(&sw_dev_mtx);
if (destroy)
swapgeom_close_ev(cp, 0);
}
static void
swapgeom_close(struct thread *td, struct swdevt *sw)
{
struct g_consumer *cp;
mtx_lock(&sw_dev_mtx);
cp = sw->sw_id;
sw->sw_id = NULL;
mtx_unlock(&sw_dev_mtx);
if (cp != NULL)
g_waitfor_event(swapgeom_close_ev, cp, M_WAITOK, NULL);
}
static int
swapongeom_locked(struct cdev *dev, struct vnode *vp)
{
struct g_provider *pp;
struct g_consumer *cp;
static struct g_geom *gp;
struct swdevt *sp;
u_long nblks;
int error;
pp = g_dev_getprovider(dev);
if (pp == NULL)
return (ENODEV);
mtx_lock(&sw_dev_mtx);
TAILQ_FOREACH(sp, &swtailq, sw_list) {
cp = sp->sw_id;
if (cp != NULL && cp->provider == pp) {
mtx_unlock(&sw_dev_mtx);
return (EBUSY);
}
}
mtx_unlock(&sw_dev_mtx);
if (gp == NULL)
gp = g_new_geomf(&g_swap_class, "swap");
cp = g_new_consumer(gp);
cp->index = 1;
cp->flags |= G_CF_DIRECT_SEND | G_CF_DIRECT_RECEIVE;
g_attach(cp, pp);
error = g_access(cp, 1, 1, 0);
if (error == 0) {
nblks = pp->mediasize / DEV_BSIZE;
error = swaponsomething(vp, cp, nblks, swapgeom_strategy,
swapgeom_close, dev2udev(dev),
(pp->flags & G_PF_ACCEPT_UNMAPPED) != 0 ? SW_UNMAPPED : 0);
if (error != 0)
g_access(cp, -1, -1, 0);
}
if (error != 0) {
g_detach(cp);
g_destroy_consumer(cp);
}
return (error);
}
static int
swapongeom(struct vnode *vp)
{
int error;
ASSERT_VOP_ELOCKED(vp, "swapongeom");
if (vp->v_type != VCHR || VN_IS_DOOMED(vp)) {
error = ENOENT;
} else {
g_topology_lock();
error = swapongeom_locked(vp->v_rdev, vp);
g_topology_unlock();
}
return (error);
}
static void
swapdev_strategy(struct buf *bp, struct swdevt *sp)
{
struct vnode *vp2;
bp->b_blkno = ctodb(bp->b_blkno - sp->sw_first);
vp2 = sp->sw_id;
vhold(vp2);
if (bp->b_iocmd == BIO_WRITE) {
vn_lock(vp2, LK_EXCLUSIVE | LK_RETRY);
if (bp->b_bufobj)
bufobj_wdrop(bp->b_bufobj);
bufobj_wref(&vp2->v_bufobj);
} else {
vn_lock(vp2, LK_SHARED | LK_RETRY);
}
if (bp->b_bufobj != &vp2->v_bufobj)
bp->b_bufobj = &vp2->v_bufobj;
bp->b_vp = vp2;
bp->b_iooffset = dbtob(bp->b_blkno);
bstrategy(bp);
VOP_UNLOCK(vp2);
}
static void
swapdev_close(struct thread *td, struct swdevt *sp)
{
struct vnode *vp;
vp = sp->sw_vp;
vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
VOP_CLOSE(vp, FREAD | FWRITE, td->td_ucred, td);
vput(vp);
}
static int
swaponvp(struct thread *td, struct vnode *vp, u_long nblks)
{
struct swdevt *sp;
int error;
ASSERT_VOP_ELOCKED(vp, "swaponvp");
if (nblks == 0)
return (ENXIO);
mtx_lock(&sw_dev_mtx);
TAILQ_FOREACH(sp, &swtailq, sw_list) {
if (sp->sw_id == vp) {
mtx_unlock(&sw_dev_mtx);
return (EBUSY);
}
}
mtx_unlock(&sw_dev_mtx);
#ifdef MAC
error = mac_system_check_swapon(td->td_ucred, vp);
if (error == 0)
#endif
error = VOP_OPEN(vp, FREAD | FWRITE, td->td_ucred, td, NULL);
if (error != 0)
return (error);
error = swaponsomething(vp, vp, nblks, swapdev_strategy, swapdev_close,
NODEV, 0);
if (error != 0)
VOP_CLOSE(vp, FREAD | FWRITE, td->td_ucred, td);
return (error);
}
static int
sysctl_swap_async_max(SYSCTL_HANDLER_ARGS)
{
int error, new, n;
new = nsw_wcount_async_max;
error = sysctl_handle_int(oidp, &new, 0, req);
if (error != 0 || req->newptr == NULL)
return (error);
if (new > nswbuf / 2 || new < 1)
return (EINVAL);
mtx_lock(&swbuf_mtx);
while (nsw_wcount_async_max != new) {
n = new - nsw_wcount_async_max;
if (nsw_wcount_async + n >= 0) {
nsw_wcount_async += n;
nsw_wcount_async_max += n;
wakeup(&nsw_wcount_async);
} else {
nsw_wcount_async_max -= nsw_wcount_async;
nsw_wcount_async = 0;
msleep(&nsw_wcount_async, &swbuf_mtx, PSWP,
"swpsysctl", 0);
}
}
mtx_unlock(&swbuf_mtx);
return (0);
}
static void
swap_pager_update_writecount(vm_object_t object, vm_offset_t start,
vm_offset_t end)
{
VM_OBJECT_WLOCK(object);
KASSERT((object->flags & OBJ_ANON) == 0,
("Splittable object with writecount"));
object->un_pager.swp.writemappings += (vm_ooffset_t)end - start;
VM_OBJECT_WUNLOCK(object);
}
static void
swap_pager_release_writecount(vm_object_t object, vm_offset_t start,
vm_offset_t end)
{
VM_OBJECT_WLOCK(object);
KASSERT((object->flags & OBJ_ANON) == 0,
("Splittable object with writecount"));
KASSERT(object->un_pager.swp.writemappings >= (vm_ooffset_t)end - start,
("swap obj %p writecount %jx dec %jx", object,
(uintmax_t)object->un_pager.swp.writemappings,
(uintmax_t)((vm_ooffset_t)end - start)));
object->un_pager.swp.writemappings -= (vm_ooffset_t)end - start;
VM_OBJECT_WUNLOCK(object);
}