When swapping to a regular file use a workqueue to signal I/O completion.

VOP_STRATEGY() no longer gets called from interrupt context via
biodone() -> sw_reg_iodone() -> sw_reg_start().

Removes a deadlock condition reported in PR 37109.

Ok: YAMAMOTO Takashi <yamt@netbsd.org>
This commit is contained in:
hannken 2007-10-15 08:12:13 +00:00
parent 1d55b1afc4
commit 988890176c
1 changed files with 47 additions and 6 deletions

View File

@ -1,4 +1,4 @@
/* $NetBSD: uvm_swap.c,v 1.129 2007/07/29 13:31:18 ad Exp $ */
/* $NetBSD: uvm_swap.c,v 1.130 2007/10/15 08:12:13 hannken Exp $ */
/*
* Copyright (c) 1995, 1996, 1997 Matthew R. Green
@ -32,7 +32,7 @@
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: uvm_swap.c,v 1.129 2007/07/29 13:31:18 ad Exp $");
__KERNEL_RCSID(0, "$NetBSD: uvm_swap.c,v 1.130 2007/10/15 08:12:13 hannken Exp $");
#include "fs_nfs.h"
#include "opt_uvmhist.h"
@ -60,6 +60,7 @@ __KERNEL_RCSID(0, "$NetBSD: uvm_swap.c,v 1.129 2007/07/29 13:31:18 ad Exp $");
#include <sys/swap.h>
#include <sys/kauth.h>
#include <sys/sysctl.h>
#include <sys/workqueue.h>
#include <uvm/uvm.h>
@ -220,6 +221,10 @@ static struct swap_priority swap_priority;
/* locks */
static krwlock_t swap_syscall_lock;
/* workqueue and use counter for swap to regular files */
static int sw_reg_count = 0;
static struct workqueue *sw_reg_workqueue;
/*
* prototypes
*/
@ -236,7 +241,8 @@ static int swap_off(struct lwp *, struct swapdev *);
static void uvm_swap_stats_locked(int, struct swapent *, int, register_t *);
static void sw_reg_strategy(struct swapdev *, struct buf *, int);
static void sw_reg_iodone(struct buf *);
static void sw_reg_biodone(struct buf *);
static void sw_reg_iodone(struct work *wk, void *dummy);
static void sw_reg_start(struct swapdev *);
static int uvm_swap_io(struct vm_page **, int, int, int);
@ -958,6 +964,18 @@ swap_on(struct lwp *l, struct swapdev *sdp)
result = vmem_alloc(swapmap, npages, VM_BESTFIT | VM_SLEEP);
if (result == 0)
panic("swapdrum_add");
/*
* If this is the first regular swap create the workqueue.
* => Protected by swap_syscall_lock.
*/
if (vp->v_type != VBLK) {
if (sw_reg_count++ == 0) {
KASSERT(sw_reg_workqueue == NULL);
if (workqueue_create(&sw_reg_workqueue, "swapiod",
sw_reg_iodone, NULL, PRIBIO, IPL_BIO, 0) != 0)
panic("swap_add: workqueue_create failed");
}
}
sdp->swd_drumoffset = (int)result;
sdp->swd_drumsize = npages;
@ -1028,6 +1046,19 @@ swap_off(struct lwp *l, struct swapdev *sdp)
return error;
}
/*
* If this is the last regular swap destroy the workqueue.
* => Protected by swap_syscall_lock.
*/
if (sdp->swd_vp->v_type != VBLK) {
KASSERT(sw_reg_count > 0);
KASSERT(sw_reg_workqueue != NULL);
if (--sw_reg_count == 0) {
workqueue_destroy(sw_reg_workqueue);
sw_reg_workqueue = NULL;
}
}
/*
* done with the vnode.
* drop our ref on the vnode before calling VOP_CLOSE()
@ -1287,7 +1318,7 @@ sw_reg_strategy(struct swapdev *sdp, struct buf *bp, int bn)
nbp->vb_buf.b_lblkno = 0;
nbp->vb_buf.b_blkno = nbn + btodb(off);
nbp->vb_buf.b_rawblkno = nbp->vb_buf.b_blkno;
nbp->vb_buf.b_iodone = sw_reg_iodone;
nbp->vb_buf.b_iodone = sw_reg_biodone;
nbp->vb_buf.b_vp = vp;
if (vp->v_type == VBLK) {
nbp->vb_buf.b_dev = vp->v_rdev;
@ -1364,19 +1395,29 @@ sw_reg_start(struct swapdev *sdp)
sdp->swd_flags &= ~SWF_BUSY;
}
/*
* sw_reg_biodone: one of our i/o's has completed
*/
static void
sw_reg_biodone(struct buf *bp)
{
workqueue_enqueue(sw_reg_workqueue, &bp->b_work, NULL);
}
/*
* sw_reg_iodone: one of our i/o's has completed and needs post-i/o cleanup
*
* => note that we can recover the vndbuf struct by casting the buf ptr
*/
static void
sw_reg_iodone(struct buf *bp)
sw_reg_iodone(struct work *wk, void *dummy)
{
struct vndbuf *vbp = (struct vndbuf *) bp;
struct vndbuf *vbp = (void *)wk;
struct vndxfer *vnx = vbp->vb_xfer;
struct buf *pbp = vnx->vx_bp; /* parent buffer */
struct swapdev *sdp = vnx->vx_sdp;
int s, resid, error;
KASSERT(&vbp->vb_buf.b_work == wk);
UVMHIST_FUNC("sw_reg_iodone"); UVMHIST_CALLED(pdhist);
UVMHIST_LOG(pdhist, " vbp=%p vp=%p blkno=%x addr=%p",