#include <sys/param.h>
#include <sys/systm.h>
#include <sys/buf.h>
#include <sys/conf.h>
#include <sys/dirent.h>
#include <sys/domain.h>
#include <sys/eventhandler.h>
#include <sys/fcntl.h>
#include <sys/kernel.h>
#include <sys/kthread.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/mount.h>
#include <sys/proc.h>
#include <sys/reboot.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/syslog.h>
#include <sys/vmmeter.h>
#include <sys/vnode.h>
#include <machine/limits.h>
#include <vm/vm.h>
#include <vm/vm_object.h>
#include <vm/vm_extern.h>
#include <vm/vm_kern.h>
#include <vm/pmap.h>
#include <vm/vm_map.h>
#include <vm/vm_page.h>
#include <vm/vm_pager.h>
#include <vm/vnode_pager.h>
#include <sys/buf2.h>
#define SYNCER_MAXDELAY 32
static int sysctl_kern_syncdelay(SYSCTL_HANDLER_ARGS);
time_t syncdelay = 30;
SYSCTL_PROC(_kern, OID_AUTO, syncdelay, CTLTYPE_INT | CTLFLAG_RW, 0, 0,
sysctl_kern_syncdelay, "I", "VFS data synchronization delay");
time_t filedelay = 30;
SYSCTL_INT(_kern, OID_AUTO, filedelay, CTLFLAG_RW,
&filedelay, 0, "File synchronization delay");
time_t dirdelay = 29;
SYSCTL_INT(_kern, OID_AUTO, dirdelay, CTLFLAG_RW,
&dirdelay, 0, "Directory synchronization delay");
time_t metadelay = 28;
SYSCTL_INT(_kern, OID_AUTO, metadelay, CTLFLAG_RW,
&metadelay, 0, "VFS metadata synchronization delay");
time_t retrydelay = 1;
SYSCTL_INT(_kern, OID_AUTO, retrydelay, CTLFLAG_RW,
&retrydelay, 0, "VFS retry synchronization delay");
static int rushjob;
static int stat_rush_requests;
SYSCTL_INT(_debug, OID_AUTO, rush_requests, CTLFLAG_RW,
&stat_rush_requests, 0, "");
LIST_HEAD(synclist, vnode);
#define SC_FLAG_EXIT (0x1)
#define SC_FLAG_DONE (0x2)
struct syncer_ctx {
struct mount *sc_mp;
struct lwkt_token sc_token;
struct thread *sc_thread;
int sc_flags;
struct synclist *syncer_workitem_pending;
long syncer_mask;
int syncer_delayno;
int syncer_forced;
int syncer_rushjob;
int syncer_trigger;
long syncer_count;
};
static void syncer_thread(void *);
static int
sysctl_kern_syncdelay(SYSCTL_HANDLER_ARGS)
{
int error;
int v = syncdelay;
error = sysctl_handle_int(oidp, &v, 0, req);
if (error || !req->newptr)
return (error);
if (v < 1)
v = 1;
if (v > SYNCER_MAXDELAY)
v = SYNCER_MAXDELAY;
syncdelay = v;
return(0);
}
long
vn_syncer_count(struct mount *mp)
{
struct syncer_ctx *ctx;
ctx = mp->mnt_syncer_ctx;
if (ctx)
return (ctx->syncer_count);
return 0;
}
void
vn_syncer_add(struct vnode *vp, int delay)
{
struct syncer_ctx *ctx;
int slot;
ctx = vp->v_mount->mnt_syncer_ctx;
lwkt_gettoken(&ctx->sc_token);
if (vp->v_flag & VONWORKLST) {
LIST_REMOVE(vp, v_synclist);
--ctx->syncer_count;
}
if (delay <= 0) {
slot = -delay & ctx->syncer_mask;
} else {
if (delay > SYNCER_MAXDELAY - 2)
delay = SYNCER_MAXDELAY - 2;
slot = (ctx->syncer_delayno + delay) & ctx->syncer_mask;
}
LIST_INSERT_HEAD(&ctx->syncer_workitem_pending[slot], vp, v_synclist);
vsetflags(vp, VONWORKLST);
++ctx->syncer_count;
lwkt_reltoken(&ctx->sc_token);
}
void
vn_syncer_remove(struct vnode *vp, int force)
{
struct syncer_ctx *ctx;
ctx = vp->v_mount->mnt_syncer_ctx;
lwkt_gettoken(&ctx->sc_token);
if ((vp->v_flag & (VISDIRTY | VONWORKLST | VOBJDIRTY)) == VONWORKLST &&
RB_EMPTY(&vp->v_rbdirty_tree))
{
vclrflags(vp, VONWORKLST);
LIST_REMOVE(vp, v_synclist);
--ctx->syncer_count;
} else if (force && (vp->v_flag & VONWORKLST)) {
vclrflags(vp, VONWORKLST);
LIST_REMOVE(vp, v_synclist);
--ctx->syncer_count;
}
lwkt_reltoken(&ctx->sc_token);
}
void
vclrisdirty(struct vnode *vp)
{
vclrflags(vp, VISDIRTY);
if (vp->v_flag & VONWORKLST)
vn_syncer_remove(vp, 0);
}
void
vclrobjdirty(struct vnode *vp)
{
vclrflags(vp, VOBJDIRTY);
if (vp->v_flag & VONWORKLST)
vn_syncer_remove(vp, 0);
}
void
vsetisdirty(struct vnode *vp)
{
struct syncer_ctx *ctx;
if ((vp->v_flag & VISDIRTY) == 0) {
ctx = vp->v_mount->mnt_syncer_ctx;
vsetflags(vp, VISDIRTY);
lwkt_gettoken(&ctx->sc_token);
if ((vp->v_flag & VONWORKLST) == 0)
vn_syncer_add(vp, syncdelay);
lwkt_reltoken(&ctx->sc_token);
}
}
void
vsetobjdirty(struct vnode *vp)
{
struct syncer_ctx *ctx;
if ((vp->v_flag & VOBJDIRTY) == 0) {
ctx = vp->v_mount->mnt_syncer_ctx;
vsetflags(vp, VOBJDIRTY);
lwkt_gettoken(&ctx->sc_token);
if ((vp->v_flag & VONWORKLST) == 0)
vn_syncer_add(vp, syncdelay);
lwkt_reltoken(&ctx->sc_token);
}
}
void
vn_syncer_thr_create(struct mount *mp)
{
struct syncer_ctx *ctx;
static int syncalloc = 0;
ctx = kmalloc(sizeof(struct syncer_ctx), M_TEMP, M_WAITOK | M_ZERO);
ctx->sc_mp = mp;
ctx->sc_flags = 0;
ctx->syncer_workitem_pending = hashinit(SYNCER_MAXDELAY, M_DEVBUF,
&ctx->syncer_mask);
ctx->syncer_delayno = 0;
lwkt_token_init(&ctx->sc_token, "syncer");
mp->mnt_syncer_ctx = ctx;
kthread_create(syncer_thread, ctx, &ctx->sc_thread,
"syncer%d", ++syncalloc & 0x7FFFFFFF);
}
void
vn_syncer_thr_stop(struct mount *mp)
{
struct syncer_ctx *ctx;
ctx = mp->mnt_syncer_ctx;
if (ctx == NULL)
return;
lwkt_gettoken(&ctx->sc_token);
ctx->sc_flags |= SC_FLAG_EXIT;
wakeup(ctx);
while ((ctx->sc_flags & SC_FLAG_DONE) == 0) {
tsleep_interlock(&ctx->sc_flags, 0);
lwkt_reltoken(&ctx->sc_token);
tsleep(&ctx->sc_flags, PINTERLOCKED, "syncexit", hz);
lwkt_gettoken(&ctx->sc_token);
}
mp->mnt_syncer_ctx = NULL;
lwkt_reltoken(&ctx->sc_token);
hashdestroy(ctx->syncer_workitem_pending, M_DEVBUF, ctx->syncer_mask);
kfree(ctx, M_TEMP);
}
struct thread *updatethread;
static void
syncer_thread(void *_ctx)
{
struct syncer_ctx *ctx = _ctx;
struct synclist *slp;
struct vnode *vp;
long starttime;
int *sc_flagsp;
int sc_flags;
int vnodes_synced = 0;
int delta;
int dummy = 0;
for (;;) {
kproc_suspend_loop();
starttime = time_uptime;
lwkt_gettoken(&ctx->sc_token);
slp = &ctx->syncer_workitem_pending[ctx->syncer_delayno];
if (ctx->syncer_trigger) {
if (ctx->sc_mp && ctx->sc_mp->mnt_syncer) {
vp = ctx->sc_mp->mnt_syncer;
if (vp->v_flag & VONWORKLST) {
vn_syncer_add(vp, retrydelay);
if (vget(vp, LK_EXCLUSIVE) == 0) {
atomic_clear_int(&ctx->syncer_trigger, 1);
VOP_FSYNC(vp, MNT_LAZY, 0);
vput(vp);
vnodes_synced++;
}
}
}
}
while ((vp = LIST_FIRST(slp)) != NULL) {
vn_syncer_add(vp, retrydelay);
if (ctx->syncer_forced) {
if (vget(vp, LK_EXCLUSIVE) == 0) {
VOP_FSYNC(vp, MNT_NOWAIT, 0);
vput(vp);
vnodes_synced++;
}
} else {
if (vget(vp, LK_EXCLUSIVE | LK_NOWAIT) == 0) {
VOP_FSYNC(vp, MNT_LAZY, 0);
vput(vp);
vnodes_synced++;
}
}
}
ctx->syncer_delayno = (ctx->syncer_delayno + 1) &
ctx->syncer_mask;
sc_flags = ctx->sc_flags;
if (sc_flags & SC_FLAG_EXIT)
break;
lwkt_reltoken(&ctx->sc_token);
if (ctx->sc_mp)
bio_ops_sync(ctx->sc_mp);
delta = rushjob - ctx->syncer_rushjob;
if ((u_int)delta > syncdelay / 2) {
ctx->syncer_rushjob = rushjob - syncdelay / 2;
tsleep(&dummy, 0, "rush", 1);
continue;
}
if (delta) {
++ctx->syncer_rushjob;
tsleep(&dummy, 0, "rush", 1);
continue;
}
if (time_uptime == starttime &&
ctx->syncer_trigger == 0) {
tsleep_interlock(ctx, 0);
if (time_uptime == starttime &&
ctx->syncer_trigger == 0 &&
(ctx->sc_flags & SC_FLAG_EXIT) == 0)
{
tsleep(ctx, PINTERLOCKED, "syncer", hz);
}
}
}
ctx->sc_flags |= SC_FLAG_DONE;
sc_flagsp = &ctx->sc_flags;
lwkt_reltoken(&ctx->sc_token);
wakeup(sc_flagsp);
kthread_exit();
}
void
vn_syncer_one(struct mount *mp)
{
struct syncer_ctx *ctx;
struct synclist *slp;
struct vnode *vp;
int i;
int n = syncdelay;
ctx = mp->mnt_syncer_ctx;
i = ctx->syncer_delayno & ctx->syncer_mask;
cpu_ccfence();
if (lwkt_trytoken(&ctx->sc_token) == 0)
return;
do {
slp = &ctx->syncer_workitem_pending[i];
vp = LIST_FIRST(slp);
if (vp && vp->v_type == VNON)
vp = LIST_NEXT(vp, v_synclist);
if (vp)
break;
i = (i + 1) & ctx->syncer_mask;
} while(--n);
if (vp) {
vn_syncer_add(vp, retrydelay);
if (vget(vp, LK_EXCLUSIVE | LK_NOWAIT) == 0) {
VOP_FSYNC(vp, MNT_LAZY, 0);
vput(vp);
}
}
lwkt_reltoken(&ctx->sc_token);
}
void
speedup_syncer(struct mount *mp)
{
atomic_add_int(&rushjob, 1);
++stat_rush_requests;
if (mp && mp->mnt_syncer_ctx)
wakeup(mp->mnt_syncer_ctx);
}
void
trigger_syncer_start(struct mount *mp)
{
struct syncer_ctx *ctx;
if (mp && (ctx = mp->mnt_syncer_ctx) != NULL) {
if (atomic_fetchadd_int(&ctx->syncer_trigger, 2) <= 1)
wakeup(ctx);
}
}
void
trigger_syncer_stop(struct mount *mp)
{
struct syncer_ctx *ctx;
if (mp && (ctx = mp->mnt_syncer_ctx) != NULL) {
atomic_add_int(&ctx->syncer_trigger, -2);
}
}
void
trigger_syncer(struct mount *mp)
{
struct syncer_ctx *ctx;
if (mp && (ctx = mp->mnt_syncer_ctx) != NULL) {
if ((ctx->syncer_trigger & 1) == 0) {
atomic_set_int(&ctx->syncer_trigger, 1);
wakeup(ctx);
}
}
}
static int sync_close(struct vop_close_args *);
static int sync_fsync(struct vop_fsync_args *);
static int sync_inactive(struct vop_inactive_args *);
static int sync_reclaim (struct vop_reclaim_args *);
static int sync_print(struct vop_print_args *);
static struct vop_ops sync_vnode_vops = {
.vop_default = vop_eopnotsupp,
.vop_close = sync_close,
.vop_fsync = sync_fsync,
.vop_inactive = sync_inactive,
.vop_reclaim = sync_reclaim,
.vop_print = sync_print,
};
static struct vop_ops *sync_vnode_vops_p = &sync_vnode_vops;
VNODEOP_SET(sync_vnode_vops);
int
vfs_allocate_syncvnode(struct mount *mp)
{
struct vnode *vp;
static long start, incr, next;
int error;
error = getspecialvnode(VT_VFS, mp, &sync_vnode_vops_p, &vp, 0, 0);
if (error) {
mp->mnt_syncer = NULL;
return (error);
}
vp->v_type = VNON;
next += incr;
if (next == 0 || next > SYNCER_MAXDELAY) {
start /= 2;
incr /= 2;
if (start == 0) {
start = SYNCER_MAXDELAY / 2;
incr = SYNCER_MAXDELAY;
}
next = start;
}
if (mp->mnt_syncer_ctx)
vn_syncer_add(vp, syncdelay > 0 ? next % syncdelay : 0);
mp->mnt_syncer = vp;
vx_unlock(vp);
return (0);
}
static int
sync_close(struct vop_close_args *ap)
{
return (0);
}
static int
sync_fsync(struct vop_fsync_args *ap)
{
struct vnode *syncvp = ap->a_vp;
struct mount *mp = syncvp->v_mount;
int asyncflag;
if ((ap->a_waitfor & MNT_LAZY) == 0)
return (0);
vn_syncer_add(syncvp, syncdelay);
if (vfs_busy(mp, LK_NOWAIT) != 0)
return (0);
if (mp->mnt_flag & MNT_RDONLY) {
vfs_msync(mp, MNT_NOWAIT);
} else {
asyncflag = mp->mnt_flag & MNT_ASYNC;
mp->mnt_flag &= ~MNT_ASYNC;
vfs_msync(mp, MNT_NOWAIT);
VFS_SYNC(mp, MNT_NOWAIT | MNT_LAZY);
if (asyncflag)
mp->mnt_flag |= MNT_ASYNC;
}
vfs_unbusy(mp);
return (0);
}
static int
sync_inactive(struct vop_inactive_args *ap)
{
vgone_vxlocked(ap->a_vp);
return (0);
}
static int
sync_reclaim(struct vop_reclaim_args *ap)
{
struct vnode *vp = ap->a_vp;
struct syncer_ctx *ctx;
ctx = vp->v_mount->mnt_syncer_ctx;
if (ctx) {
lwkt_gettoken(&ctx->sc_token);
KKASSERT(vp->v_mount->mnt_syncer != vp);
if (vp->v_flag & VONWORKLST) {
LIST_REMOVE(vp, v_synclist);
vclrflags(vp, VONWORKLST);
--ctx->syncer_count;
}
lwkt_reltoken(&ctx->sc_token);
} else {
KKASSERT((vp->v_flag & VONWORKLST) == 0);
}
return (0);
}
int
vsyncscan(
struct mount *mp,
int vmsc_flags,
int (*slowfunc)(struct mount *mp, struct vnode *vp, void *data),
void *data
) {
struct syncer_ctx *ctx;
struct synclist *slp;
struct vnode *vp;
int i;
int count;
int lkflags;
if (vmsc_flags & VMSC_NOWAIT)
lkflags = LK_NOWAIT;
else
lkflags = 0;
KKASSERT(mp->mnt_kern_flag & MNTK_THR_SYNC);
ctx = mp->mnt_syncer_ctx;
lwkt_gettoken(&ctx->sc_token);
i = ctx->syncer_delayno & ctx->syncer_mask;
if ((vmsc_flags & VMSC_NOWAIT) == 0)
++ctx->syncer_forced;
for (count = 0; count <= ctx->syncer_mask; ++count) {
slp = &ctx->syncer_workitem_pending[i];
while ((vp = LIST_FIRST(slp)) != NULL) {
KKASSERT(vp->v_mount == mp);
if (vmsc_flags & VMSC_GETVP) {
if (vget(vp, LK_EXCLUSIVE | lkflags) == 0) {
slowfunc(mp, vp, data);
vput(vp);
}
} else if (vmsc_flags & VMSC_GETVX) {
vx_get(vp);
slowfunc(mp, vp, data);
vx_put(vp);
} else {
vhold(vp);
slowfunc(mp, vp, data);
vdrop(vp);
}
if (LIST_FIRST(slp) == vp)
vn_syncer_add(vp, -(i + syncdelay));
}
i = (i + 1) & ctx->syncer_mask;
}
if ((vmsc_flags & VMSC_NOWAIT) == 0)
--ctx->syncer_forced;
lwkt_reltoken(&ctx->sc_token);
return(0);
}
static int
sync_print(struct vop_print_args *ap)
{
struct vnode *vp = ap->a_vp;
kprintf("syncer vnode");
lockmgr_printinfo(&vp->v_lock);
kprintf("\n");
return (0);
}