Make vnd do I/O to the underlying file from thread context. This

allows the strategy routine to be called from interrupt context, fixes
PR kern/29775 by Juan RP.
Now that pool_get() is only called from thread context, change PR_NOWAIT to
PR_WAITOK. Fix PR kern/26272 by Juergen Hannken-Illjes.
OK'd by thorpej@
This commit is contained in:
bouyer 2005-03-30 19:23:08 +00:00
parent 7174de81d3
commit 007f04c4a8
2 changed files with 261 additions and 206 deletions

View File

@ -1,4 +1,4 @@
/* $NetBSD: vnd.c,v 1.111 2004/10/28 07:07:39 yamt Exp $ */ /* $NetBSD: vnd.c,v 1.112 2005/03/30 19:23:08 bouyer Exp $ */
/*- /*-
* Copyright (c) 1996, 1997, 1998 The NetBSD Foundation, Inc. * Copyright (c) 1996, 1997, 1998 The NetBSD Foundation, Inc.
@ -133,7 +133,7 @@
*/ */
#include <sys/cdefs.h> #include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: vnd.c,v 1.111 2004/10/28 07:07:39 yamt Exp $"); __KERNEL_RCSID(0, "$NetBSD: vnd.c,v 1.112 2005/03/30 19:23:08 bouyer Exp $");
#if defined(_KERNEL_OPT) #if defined(_KERNEL_OPT)
#include "fs_nfs.h" #include "fs_nfs.h"
@ -143,6 +143,7 @@ __KERNEL_RCSID(0, "$NetBSD: vnd.c,v 1.111 2004/10/28 07:07:39 yamt Exp $");
#include <sys/systm.h> #include <sys/systm.h>
#include <sys/namei.h> #include <sys/namei.h>
#include <sys/proc.h> #include <sys/proc.h>
#include <sys/kthread.h>
#include <sys/errno.h> #include <sys/errno.h>
#include <sys/buf.h> #include <sys/buf.h>
#include <sys/bufq.h> #include <sys/bufq.h>
@ -190,10 +191,10 @@ struct vndbuf {
struct vndxfer *vb_xfer; struct vndxfer *vb_xfer;
}; };
#define VND_GETXFER(vnd) pool_get(&(vnd)->sc_vxpool, PR_NOWAIT) #define VND_GETXFER(vnd) pool_get(&(vnd)->sc_vxpool, PR_WAITOK)
#define VND_PUTXFER(vnd, vx) pool_put(&(vnd)->sc_vxpool, (vx)) #define VND_PUTXFER(vnd, vx) pool_put(&(vnd)->sc_vxpool, (vx))
#define VND_GETBUF(vnd) pool_get(&(vnd)->sc_vbpool, PR_NOWAIT) #define VND_GETBUF(vnd) pool_get(&(vnd)->sc_vbpool, PR_WAITOK)
#define VND_PUTBUF(vnd, vb) pool_put(&(vnd)->sc_vbpool, (vb)) #define VND_PUTBUF(vnd, vb) pool_put(&(vnd)->sc_vbpool, (vb))
struct vnd_softc *vnd_softc; struct vnd_softc *vnd_softc;
@ -207,7 +208,6 @@ void vndattach(int);
int vnddetach(void); int vnddetach(void);
static void vndclear(struct vnd_softc *, int); static void vndclear(struct vnd_softc *, int);
static void vndstart(struct vnd_softc *);
static int vndsetcred(struct vnd_softc *, struct ucred *); static int vndsetcred(struct vnd_softc *, struct ucred *);
static void vndthrottle(struct vnd_softc *, struct vnode *); static void vndthrottle(struct vnd_softc *, struct vnode *);
static void vndiodone(struct buf *); static void vndiodone(struct buf *);
@ -221,6 +221,8 @@ static void vndgetdisklabel(dev_t);
static int vndlock(struct vnd_softc *); static int vndlock(struct vnd_softc *);
static void vndunlock(struct vnd_softc *); static void vndunlock(struct vnd_softc *);
void vndthread(void *);
static dev_type_open(vndopen); static dev_type_open(vndopen);
static dev_type_close(vndclose); static dev_type_close(vndclose);
static dev_type_read(vndread); static dev_type_read(vndread);
@ -393,38 +395,24 @@ vndclose(dev_t dev, int flags, int mode, struct proc *p)
} }
/* /*
* Break the request into bsize pieces and submit using VOP_BMAP/VOP_STRATEGY. * Qeue the request, and wakeup the kernel thread to handle it.
*/ */
static void static void
vndstrategy(struct buf *bp) vndstrategy(struct buf *bp)
{ {
int unit = vndunit(bp->b_dev); int unit = vndunit(bp->b_dev);
struct vnd_softc *vnd = &vnd_softc[unit]; struct vnd_softc *vnd = &vnd_softc[unit];
struct vndxfer *vnx; struct disklabel *lp = vnd->sc_dkdev.dk_label;
struct mount *mp; int s = splbio();
int s, bsize, resid;
off_t bn; bp->b_resid = bp->b_bcount;
caddr_t addr;
int sz, flags, error, wlabel;
struct disklabel *lp;
struct partition *pp;
#ifdef DEBUG
if (vnddebug & VDB_FOLLOW)
printf("vndstrategy(%p): unit %d\n", bp, unit);
#endif
if ((vnd->sc_flags & VNF_INITED) == 0) { if ((vnd->sc_flags & VNF_INITED) == 0) {
bp->b_error = ENXIO; bp->b_error = ENXIO;
bp->b_flags |= B_ERROR; bp->b_flags |= B_ERROR;
goto done; goto done;
} }
/* If it's a nil transfer, wake up the top half now. */
if (bp->b_bcount == 0)
goto done;
lp = vnd->sc_dkdev.dk_label;
/* /*
* The transfer must be a whole number of blocks. * The transfer must be a whole number of blocks.
*/ */
@ -434,15 +422,6 @@ vndstrategy(struct buf *bp)
goto done; goto done;
} }
/*
* Do bounds checking and adjust transfer. If there's an error,
* the bounds check will flag that for us.
*/
wlabel = vnd->sc_flags & (VNF_WLABEL|VNF_LABELLING);
if (DISKPART(bp->b_dev) != RAW_PART)
if (bounds_check_with_label(&vnd->sc_dkdev, bp, wlabel) <= 0)
goto done;
/* /*
* check if we're read-only. * check if we're read-only.
*/ */
@ -452,194 +431,249 @@ vndstrategy(struct buf *bp)
goto done; goto done;
} }
bp->b_resid = bp->b_bcount;
/* /*
* Put the block number in terms of the logical blocksize * Do bounds checking and adjust transfer. If there's an error,
* of the "device". * the bounds check will flag that for us.
*/
bn = bp->b_blkno / (lp->d_secsize / DEV_BSIZE);
/*
* Translate the partition-relative block number to an absolute.
*/ */
if (DISKPART(bp->b_dev) != RAW_PART) { if (DISKPART(bp->b_dev) != RAW_PART) {
pp = &vnd->sc_dkdev.dk_label->d_partitions[DISKPART(bp->b_dev)]; if (bounds_check_with_label(&vnd->sc_dkdev,
bn += pp->p_offset; bp, vnd->sc_flags & (VNF_WLABEL|VNF_LABELLING)) <= 0)
goto done;
} }
/* ...and convert to a byte offset within the file. */ /* If it's a nil transfer, wake up the top half now. */
bn *= lp->d_secsize; if (bp->b_bcount == 0)
if (vnd->sc_vp->v_mount == NULL) {
bp->b_error = ENXIO;
bp->b_flags |= B_ERROR;
goto done; goto done;
}
bsize = vnd->sc_vp->v_mount->mnt_stat.f_iosize;
addr = bp->b_data;
flags = (bp->b_flags & (B_READ|B_ASYNC)) | B_CALL;
/* Allocate a header for this transfer and link it to the buffer */
s = splbio();
vnx = VND_GETXFER(vnd);
splx(s);
vnx->vx_flags = VX_BUSY;
vnx->vx_error = 0;
vnx->vx_pending = 0;
vnx->vx_bp = bp;
if ((flags & B_READ) == 0)
vn_start_write(vnd->sc_vp, &mp, V_WAIT);
for (resid = bp->b_resid; resid; resid -= sz) {
struct vndbuf *nbp;
struct vnode *vp;
daddr_t nbn;
int off, nra;
nra = 0;
vn_lock(vnd->sc_vp, LK_EXCLUSIVE | LK_RETRY | LK_CANRECURSE);
error = VOP_BMAP(vnd->sc_vp, bn / bsize, &vp, &nbn, &nra);
VOP_UNLOCK(vnd->sc_vp, 0);
if (error == 0 && (long)nbn == -1)
error = EIO;
/*
* If there was an error or a hole in the file...punt.
* Note that we may have to wait for any operations
* that we have already fired off before releasing
* the buffer.
*
* XXX we could deal with holes here but it would be
* a hassle (in the write case).
*/
if (error) {
s = splbio();
vnx->vx_error = error;
goto out;
}
#ifdef DEBUG #ifdef DEBUG
if (!dovndcluster) if (vnddebug & VDB_FOLLOW)
nra = 0; printf("vndstrategy(%p): unit %d\n", bp, unit);
#endif #endif
BUFQ_PUT(&vnd->sc_tab, bp);
if ((off = bn % bsize) != 0) wakeup(&vnd->sc_tab);
sz = bsize - off;
else
sz = (1 + nra) * bsize;
if (resid < sz)
sz = resid;
#ifdef DEBUG
if (vnddebug & VDB_IO)
printf("vndstrategy: vp %p/%p bn 0x%qx/0x%" PRIx64
" sz 0x%x\n",
vnd->sc_vp, vp, (long long)bn, nbn, sz);
#endif
s = splbio();
nbp = VND_GETBUF(vnd);
splx(s);
BUF_INIT(&nbp->vb_buf);
nbp->vb_buf.b_flags = flags;
nbp->vb_buf.b_bcount = sz;
nbp->vb_buf.b_bufsize = round_page((ulong)addr + sz)
- trunc_page((ulong) addr);
nbp->vb_buf.b_error = 0;
nbp->vb_buf.b_data = addr;
nbp->vb_buf.b_blkno = nbp->vb_buf.b_rawblkno = nbn + btodb(off);
nbp->vb_buf.b_proc = bp->b_proc;
nbp->vb_buf.b_iodone = vndiodone;
nbp->vb_buf.b_vp = vp;
nbp->vb_xfer = vnx;
BIO_COPYPRIO(&nbp->vb_buf, bp);
/*
* Just sort by block number
*/
s = splbio();
if (vnx->vx_error != 0) {
VND_PUTBUF(vnd, nbp);
goto out;
}
vnx->vx_pending++;
BUFQ_PUT(&vnd->sc_tab, &nbp->vb_buf);
vndstart(vnd);
splx(s);
bn += sz;
addr += sz;
}
s = splbio();
out: /* Arrive here at splbio */
if ((flags & B_READ) == 0)
vn_finished_write(mp, 0);
vnx->vx_flags &= ~VX_BUSY;
if (vnx->vx_pending == 0) {
if (vnx->vx_error != 0) {
bp->b_error = vnx->vx_error;
bp->b_flags |= B_ERROR;
}
VND_PUTXFER(vnd, vnx);
biodone(bp);
}
splx(s); splx(s);
return; return;
done:
done:
biodone(bp); biodone(bp);
splx(s);
} }
/* void
* Feed requests sequentially. vndthread(void *arg)
* We do it this way to keep from flooding NFS servers if we are connected
* to an NFS file. This places the burden on the client rather than the
* server.
*/
static void
vndstart(struct vnd_softc *vnd)
{ {
struct buf *bp; struct vnd_softc *vnd = arg;
struct buf *bp;
struct vndxfer *vnx;
struct mount *mp;
int s, bsize, resid;
off_t bn;
caddr_t addr;
int sz, flags, error;
struct disklabel *lp;
struct partition *pp;
s = splbio();
vnd->sc_flags |= VNF_KTHREAD;
wakeup(&vnd->sc_kthread);
/* /*
* Dequeue now since lower level strategy routine might * Dequeue requests, break them into bsize pieces and submit using
* queue using same links * VOP_BMAP/VOP_STRATEGY.
*/ */
while ((vnd->sc_flags & VNF_VUNCONF) == 0) {
if ((vnd->sc_flags & VNF_BUSY) != 0)
return;
vnd->sc_flags |= VNF_BUSY;
while (vnd->sc_active < vnd->sc_maxactive) {
bp = BUFQ_GET(&vnd->sc_tab); bp = BUFQ_GET(&vnd->sc_tab);
if (bp == NULL) if (bp == NULL) {
break; tsleep(&vnd->sc_tab, PRIBIO, "vndbp", 0);
vnd->sc_active++; continue;
};
splx(s);
#ifdef DEBUG #ifdef DEBUG
if (vnddebug & VDB_IO) if (vnddebug & VDB_FOLLOW)
printf("vndstart(%ld): bp %p vp %p blkno 0x%" PRIx64 printf("vndthread(%p\n", bp);
" flags %x addr %p cnt 0x%x\n", #endif
(long) (vnd-vnd_softc), bp, bp->b_vp, bp->b_blkno, lp = vnd->sc_dkdev.dk_label;
bp->b_flags, bp->b_data, bp->b_bcount); bp->b_resid = bp->b_bcount;
/*
* Put the block number in terms of the logical blocksize
* of the "device".
*/
bn = bp->b_blkno / (lp->d_secsize / DEV_BSIZE);
/*
* Translate the partition-relative block number to an absolute.
*/
if (DISKPART(bp->b_dev) != RAW_PART) {
pp = &vnd->sc_dkdev.dk_label->d_partitions[
DISKPART(bp->b_dev)];
bn += pp->p_offset;
}
/* ...and convert to a byte offset within the file. */
bn *= lp->d_secsize;
if (vnd->sc_vp->v_mount == NULL) {
bp->b_error = ENXIO;
bp->b_flags |= B_ERROR;
goto done;
}
bsize = vnd->sc_vp->v_mount->mnt_stat.f_iosize;
addr = bp->b_data;
flags = (bp->b_flags & (B_READ|B_ASYNC)) | B_CALL;
/*
* Allocate a header for this transfer and link it to the
* buffer
*/
s = splbio();
vnx = VND_GETXFER(vnd);
splx(s);
vnx->vx_flags = VX_BUSY;
vnx->vx_error = 0;
vnx->vx_pending = 0;
vnx->vx_bp = bp;
if ((flags & B_READ) == 0)
vn_start_write(vnd->sc_vp, &mp, V_WAIT);
/*
* Feed requests sequentially.
* We do it this way to keep from flooding NFS servers if we
* are connected to an NFS file. This places the burden on
* the client rather than the server.
*/
for (resid = bp->b_resid; resid; resid -= sz) {
struct vndbuf *nbp;
struct vnode *vp;
daddr_t nbn;
int off, nra;
nra = 0;
vn_lock(vnd->sc_vp, LK_EXCLUSIVE | LK_RETRY | LK_CANRECURSE);
error = VOP_BMAP(vnd->sc_vp, bn / bsize, &vp, &nbn, &nra);
VOP_UNLOCK(vnd->sc_vp, 0);
if (error == 0 && (long)nbn == -1)
error = EIO;
/*
* If there was an error or a hole in the file...punt.
* Note that we may have to wait for any operations
* that we have already fired off before releasing
* the buffer.
*
* XXX we could deal with holes here but it would be
* a hassle (in the write case).
*/
if (error) {
s = splbio();
vnx->vx_error = error;
goto out;
}
#ifdef DEBUG
if (!dovndcluster)
nra = 0;
#endif #endif
/* Instrumentation. */ if ((off = bn % bsize) != 0)
disk_busy(&vnd->sc_dkdev); sz = bsize - off;
else
sz = (1 + nra) * bsize;
if (resid < sz)
sz = resid;
#ifdef DEBUG
if (vnddebug & VDB_IO)
printf("vndstrategy: vp %p/%p bn 0x%qx/0x%" PRIx64
" sz 0x%x\n",
vnd->sc_vp, vp, (long long)bn, nbn, sz);
#endif
if ((bp->b_flags & B_READ) == 0) s = splbio();
bp->b_vp->v_numoutput++; while (vnd->sc_active >= vnd->sc_maxactive) {
VOP_STRATEGY(bp->b_vp, bp); tsleep(&vnd->sc_tab, PRIBIO, "vndac", 0);
if (vnd->sc_flags & VNF_VUNCONF)
goto kthread_end;
}
vnd->sc_active++;
nbp = VND_GETBUF(vnd);
splx(s);
BUF_INIT(&nbp->vb_buf);
nbp->vb_buf.b_flags = flags;
nbp->vb_buf.b_bcount = sz;
nbp->vb_buf.b_bufsize = round_page((ulong)addr + sz)
- trunc_page((ulong) addr);
nbp->vb_buf.b_error = 0;
nbp->vb_buf.b_data = addr;
nbp->vb_buf.b_blkno = nbp->vb_buf.b_rawblkno = nbn + btodb(off);
nbp->vb_buf.b_proc = bp->b_proc;
nbp->vb_buf.b_iodone = vndiodone;
nbp->vb_buf.b_vp = vp;
nbp->vb_xfer = vnx;
BIO_COPYPRIO(&nbp->vb_buf, bp);
/*
* Just sort by block number
*/
s = splbio();
if (vnx->vx_error != 0) {
VND_PUTBUF(vnd, nbp);
goto out;
}
vnx->vx_pending++;
#ifdef DEBUG
if (vnddebug & VDB_IO)
printf("vndstart(%ld): bp %p vp %p blkno "
"0x%" PRIx64 " flags %x addr %p cnt 0x%x\n",
(long) (vnd-vnd_softc), &nbp->vb_buf,
nbp->vb_buf.b_vp, nbp->vb_buf.b_blkno,
nbp->vb_buf.b_flags, nbp->vb_buf.b_data,
nbp->vb_buf.b_bcount);
#endif
/* Instrumentation. */
disk_busy(&vnd->sc_dkdev);
if ((nbp->vb_buf.b_flags & B_READ) == 0)
vp->v_numoutput++;
VOP_STRATEGY(vp, &nbp->vb_buf);
splx(s);
bn += sz;
addr += sz;
}
s = splbio();
out: /* Arrive here at splbio */
if ((flags & B_READ) == 0)
vn_finished_write(mp, 0);
vnx->vx_flags &= ~VX_BUSY;
if (vnx->vx_pending == 0) {
if (vnx->vx_error != 0) {
bp->b_error = vnx->vx_error;
bp->b_flags |= B_ERROR;
}
VND_PUTXFER(vnd, vnx);
biodone(bp);
}
continue;
done:
biodone(bp);
s = splbio();
continue;
} }
vnd->sc_flags &= ~VNF_BUSY;
kthread_end:
vnd->sc_flags &= (~VNF_KTHREAD | VNF_VUNCONF);
wakeup(&vnd->sc_kthread);
splx(s);
kthread_exit(0);
} }
static void static void
vndiodone(struct buf *bp) vndiodone(struct buf *bp)
{ {
@ -710,7 +744,7 @@ vndiodone(struct buf *bp)
} }
vnd->sc_active--; vnd->sc_active--;
vndstart(vnd); wakeup(&vnd->sc_tab);
splx(s); splx(s);
} }
@ -906,9 +940,23 @@ vndioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
if ((error = vndsetcred(vnd, p->p_ucred)) != 0) if ((error = vndsetcred(vnd, p->p_ucred)) != 0)
goto close_and_exit; goto close_and_exit;
memset(vnd->sc_xname, 0, sizeof(vnd->sc_xname)); /* XXX */
snprintf(vnd->sc_xname, sizeof(vnd->sc_xname), "vnd%d", unit);
vndthrottle(vnd, vnd->sc_vp); vndthrottle(vnd, vnd->sc_vp);
vio->vnd_size = dbtob(vnd->sc_size); vio->vnd_size = dbtob(vnd->sc_size);
vnd->sc_flags |= VNF_INITED; vnd->sc_flags |= VNF_INITED;
/* create the kernel thread, wait for it to be up */
error = kthread_create1(vndthread, vnd, &vnd->sc_kthread,
vnd->sc_xname);
if (error)
goto close_and_exit;
while ((vnd->sc_flags & VNF_KTHREAD) == 0) {
tsleep(&vnd->sc_kthread, PRIBIO, "vndthr", 0);
}
#ifdef DEBUG #ifdef DEBUG
if (vnddebug & VDB_INIT) if (vnddebug & VDB_INIT)
printf("vndioctl: SET vp %p size 0x%lx %d/%d/%d/%d\n", printf("vndioctl: SET vp %p size 0x%lx %d/%d/%d/%d\n",
@ -920,8 +968,6 @@ vndioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
#endif #endif
/* Attach the disk. */ /* Attach the disk. */
memset(vnd->sc_xname, 0, sizeof(vnd->sc_xname)); /* XXX */
snprintf(vnd->sc_xname, sizeof(vnd->sc_xname), "vnd%d", unit);
vnd->sc_dkdev.dk_name = vnd->sc_xname; vnd->sc_dkdev.dk_name = vnd->sc_xname;
disk_attach(&vnd->sc_dkdev); disk_attach(&vnd->sc_dkdev);
@ -1214,9 +1260,16 @@ vndclear(struct vnd_softc *vnd, int myminor)
if ((vnd->sc_flags & VNF_READONLY) == 0) if ((vnd->sc_flags & VNF_READONLY) == 0)
fflags |= FWRITE; fflags |= FWRITE;
vnd->sc_flags &= ~(VNF_INITED | VNF_READONLY | VNF_VLABEL);
vnd->sc_flags |= VNF_VUNCONF;
wakeup(&vnd->sc_tab);
while (vnd->sc_flags & VNF_KTHREAD)
tsleep(&vnd->sc_kthread, PRIBIO, "vnthr", 0);
vnd->sc_flags &=
~(VNF_INITED | VNF_READONLY | VNF_VLABEL | VNF_VUNCONF);
if (vp == (struct vnode *)0) if (vp == (struct vnode *)0)
panic("vndioctl: null vp"); panic("vndclear: null vp");
(void) vn_close(vp, fflags, vnd->sc_cred, p); (void) vn_close(vp, fflags, vnd->sc_cred, p);
crfree(vnd->sc_cred); crfree(vnd->sc_cred);
vnd->sc_vp = (struct vnode *)0; vnd->sc_vp = (struct vnode *)0;

View File

@ -1,4 +1,4 @@
/* $NetBSD: vndvar.h,v 1.13 2005/02/27 00:26:58 perry Exp $ */ /* $NetBSD: vndvar.h,v 1.14 2005/03/30 19:23:08 bouyer Exp $ */
/*- /*-
* Copyright (c) 1996, 1997, 1998 The NetBSD Foundation, Inc. * Copyright (c) 1996, 1997, 1998 The NetBSD Foundation, Inc.
@ -162,6 +162,7 @@ struct vnd_softc {
struct vndgeom sc_geom; /* virtual geometry */ struct vndgeom sc_geom; /* virtual geometry */
struct pool sc_vxpool; /* vndxfer pool */ struct pool sc_vxpool; /* vndxfer pool */
struct pool sc_vbpool; /* vndbuf pool */ struct pool sc_vbpool; /* vndbuf pool */
struct proc *sc_kthread; /* kernel thread */
}; };
#endif #endif
@ -171,10 +172,11 @@ struct vnd_softc {
#define VNF_LABELLING 0x004 /* unit is currently being labelled */ #define VNF_LABELLING 0x004 /* unit is currently being labelled */
#define VNF_WANTED 0x008 /* someone is waiting to obtain a lock */ #define VNF_WANTED 0x008 /* someone is waiting to obtain a lock */
#define VNF_LOCKED 0x010 /* unit is locked */ #define VNF_LOCKED 0x010 /* unit is locked */
#define VNF_BUSY 0x020 /* unit is busy */ #define VNF_READONLY 0x020 /* unit is read-only */
#define VNF_READONLY 0x040 /* unit is read-only */ #define VNF_KLABEL 0x040 /* keep label on close */
#define VNF_KLABEL 0x080 /* keep label on close */ #define VNF_VLABEL 0x080 /* label is valid */
#define VNF_VLABEL 0x100 /* label is valid */ #define VNF_KTHREAD 0x100 /* thread is running */
#define VNF_VUNCONF 0x200 /* device is unconfiguring */
/* /*
* A simple structure for describing which vnd units are in use. * A simple structure for describing which vnd units are in use.