kernel - More buffer cache / VM coherency work
authorMatthew Dillon <dillon@apollo.backplane.com>
Tue, 26 Jan 2010 20:41:03 +0000 (12:41 -0800)
committerMatthew Dillon <dillon@apollo.backplane.com>
Tue, 26 Jan 2010 20:41:03 +0000 (12:41 -0800)
* Add a buffer offset argument to nvtruncbuf().  The truncation length and
  blocksize for the block containing the truncation point alone are
  insufficient since prior blocks might be using a different blocksize.

* Add a buffer offset argument to nvnode_pager_setsize() for the same
  reason.

* nvtruncbuf() and nvextendbuf() now bdwrite() the buffer being zero-filled.
  This fixes a race where the clean buffer might be discarded and read
  from the medias pre-truncation backing store again before the filesystem
  has a chance to adjust it.

* nvextendbuf() now takes additional arguments.  The block offset for the
  old and new blocks must be passed.

* Convert UFS over to the use nv*() API, hopefully solving any remaining
  fsx VM/BUF coherency issues.

* Correct bugs with swap_burst_read mode, but leave the mode disabled.
  There are still unresolved issues when the mode is enabled.
  (Reported-by: YONETANI Tomokazu <qhwt+dfly@les.ath.cx>)

* Fix a bug in vm_prefault() which would leak VM pages, eventually
  causing the machine to run out of memory.

sys/kern/vfs_vm.c
sys/sys/vnode.h
sys/vfs/nfs/nfs_bio.c
sys/vfs/nfs/nfs_subs.c
sys/vfs/ufs/ffs_inode.c
sys/vfs/ufs/fs.h
sys/vfs/ufs/ufs_lookup.c
sys/vfs/ufs/ufs_readwrite.c
sys/vfs/ufs/ufs_vnops.c
sys/vm/swap_pager.c
sys/vm/vm_fault.c

index 5cae130..02e9f8d 100644 (file)
@@ -103,9 +103,22 @@ static int nvtruncbuf_bp_metasync(struct buf *bp, void *data);
  * as appropriate.  All buffers and pages after the last buffer will be
  * destroyed.  The last buffer itself will be destroyed only if the length
  * is exactly aligned with it.
+ *
+ * UFS typically passes the old block size prior to the actual truncation,
+ * then later resizes the block based on the new file size.  NFS uses a
+ * fixed block size and doesn't care.  HAMMER uses a block size based on
+ * the offset which is fixed for any particular offset.
+ *
+ * When zero-filling we must bdwrite() to avoid a window of opportunity
+ * where the kernel might throw away a clean buffer and the filesystem
+ * then attempts to bread() it again before completing (or as part of)
+ * the extension.  The filesystem is still responsible for zero-filling
+ * any remainder when writing to the media in the strategy function when
+ * it is able to do so without the page being mapped.  The page may still
+ * be mapped by userland here.
  */
 int
-nvtruncbuf(struct vnode *vp, off_t length, int blksize)
+nvtruncbuf(struct vnode *vp, off_t length, int blksize, int boff)
 {
        off_t truncloffset;
        off_t truncboffset;
@@ -113,7 +126,6 @@ nvtruncbuf(struct vnode *vp, off_t length, int blksize)
        lwkt_tokref vlock;
        struct buf *bp;
        int count;
-       int boff;
        int error;
 
        /*
@@ -123,7 +135,8 @@ nvtruncbuf(struct vnode *vp, off_t length, int blksize)
         *
         * Destroy any pages beyond the last buffer.
         */
-       boff = (int)(length % blksize);
+       if (boff < 0)
+               boff = (int)(length % blksize);
        if (boff)
                truncloffset = length + (blksize - boff);
        else
@@ -139,13 +152,14 @@ nvtruncbuf(struct vnode *vp, off_t length, int blksize)
                                nvtruncbuf_bp_trunc, &truncloffset);
        } while(count);
 
-       nvnode_pager_setsize(vp, length, blksize);
+       nvnode_pager_setsize(vp, length, blksize, boff);
 
        /*
         * Zero-fill the area beyond the file EOF that still fits within
-        * the last buffer.  Even though we are modifying the contents
-        * of a buffer we are doing so beyond the file EOF and it doesn't
-        * count as a real modification.
+        * the last buffer.  We must mark the buffer as dirty even though
+        * the modified area is beyond EOF to avoid races where the kernel
+        * might flush the buffer before the filesystem is able to reallocate
+        * the block.
         *
         * The VFS is responsible for dealing with the actual truncation.
         */
@@ -160,7 +174,7 @@ nvtruncbuf(struct vnode *vp, off_t length, int blksize)
                                if (bp->b_dirtyend > boff)
                                        bp->b_dirtyend = boff;
                        }
-                       bqrelse(bp);
+                       bdwrite(bp);
                }
        } else {
                error = 0;
@@ -171,6 +185,9 @@ nvtruncbuf(struct vnode *vp, off_t length, int blksize)
         * truncated to 0.  Since the metadata does not represent the entire
         * dirty list we have to rely on the hit count to ensure that we get
         * all of it.
+        *
+        * This is typically applicable only to UFS.  NFS and HAMMER do
+        * not store indirect blocks in the per-vnode buffer cache.
         */
        if (length > 0) {
                do {
@@ -292,42 +309,47 @@ nvtruncbuf_bp_metasync(struct buf *bp, void *data)
 }
 
 /*
- * Extend a file's buffer and pages to a new, larger size.  Note that the
- * blocksize passed is for the buffer covering the old file size, NOT the
- * new file size.
+ * Extend a file's buffer and pages to a new, larger size.  The block size
+ * at both the old and new length must be passed, but buffer cache operations
+ * will only be performed on the old block.  The new nlength/nblksize will
+ * be used to properly set the VM object size.
  *
  * To make this explicit we require the old length to passed even though
- * we can acquire it from vp->v_filesize.
+ * we can acquire it from vp->v_filesize, which also avoids potential
+ * corruption if the filesystem and vp get desynchronized somehow.
  *
  * If the caller intends to immediately write into the newly extended
  * space pass trivial == 1.  If trivial is 0 the original buffer will be
  * zero-filled as necessary to clean out any junk in the extended space.
  *
- * NOTE: We do not zero-fill to the end of the buffer or page to remove
- *      mmap cruft since userland can just re-cruft it.  Filesystems are
- *      responsible for zero-filling extra space beyond the file EOF during
- *      strategy write functions, or zero-filling junk areas on read.
+ * When zero-filling we must bdwrite() to avoid a window of opportunity
+ * where the kernel might throw away a clean buffer and the filesystem
+ * then attempts to bread() it again before completing (or as part of)
+ * the extension.  The filesystem is still responsible for zero-filling
+ * any remainder when writing to the media in the strategy function when
+ * it is able to do so without the page being mapped.  The page may still
+ * be mapped by userland here.
  */
 int
 nvextendbuf(struct vnode *vp, off_t olength, off_t nlength,
-           int oblksize, int nblksize, int trivial)
+           int oblksize, int nblksize, int oboff, int nboff, int trivial)
 {
        off_t truncboffset;
        struct buf *bp;
-       int boff;
        int error;
 
        error = 0;
-       nvnode_pager_setsize(vp, nlength, nblksize);
+       nvnode_pager_setsize(vp, nlength, nblksize, nboff);
        if (trivial == 0) {
-               boff = (int)(olength % oblksize);
-               truncboffset = olength - boff;
+               if (oboff < 0)
+                       oboff = (int)(olength % oblksize);
+               truncboffset = olength - oboff;
 
-               if (boff) {
+               if (oboff) {
                        error = bread(vp, truncboffset, oblksize, &bp);
                        if (error == 0) {
-                               bzero(bp->b_data + boff, oblksize - boff);
-                               bqrelse(bp);
+                               bzero(bp->b_data + oboff, oblksize - oboff);
+                               bdwrite(bp);
                        }
                }
        }
@@ -342,9 +364,17 @@ nvextendbuf(struct vnode *vp, off_t olength, off_t nlength,
  * overlapping pages.  Zeroing is the responsibility of nvtruncbuf().
  * However, it does unmap VM pages from the user address space on a
  * page-granular (verses buffer cache granular) basis.
+ *
+ * If boff is passed as -1 the base offset of the buffer cache buffer is
+ * calculated from length and blksize.  Filesystems such as UFS which deal
+ * with fragments have to specify a boff >= 0 since the base offset cannot
+ * be calculated from length and blksize.
+ *
+ * For UFS blksize is the 'new' blocksize, used only to determine how large
+ * the VM object must become.
  */
 void
-nvnode_pager_setsize(struct vnode *vp, off_t length, int blksize)
+nvnode_pager_setsize(struct vnode *vp, off_t length, int blksize, int boff)
 {
        vm_pindex_t nobjsize;
        vm_pindex_t oobjsize;
@@ -352,7 +382,6 @@ nvnode_pager_setsize(struct vnode *vp, off_t length, int blksize)
        vm_object_t object;
        vm_page_t m;
        off_t truncboffset;
-       int boff;
 
        /*
         * Degenerate conditions
@@ -370,7 +399,8 @@ nvnode_pager_setsize(struct vnode *vp, off_t length, int blksize)
         * Buffers do not have to be page-aligned.  Make sure
         * nobjsize is beyond the last page of the buffer.
         */
-       boff = (int)(length % blksize);
+       if (boff < 0)
+               boff = (int)(length % blksize);
        truncboffset = length - boff;
        oobjsize = object->size;
        if (boff)
index 1d1c8de..5bf78f4 100644 (file)
@@ -506,10 +506,12 @@ int       vrevoke (struct vnode *vp, struct ucred *cred);
 int    vinvalbuf (struct vnode *vp, int save, int slpflag, int slptimeo);
 int    vtruncbuf (struct vnode *vp, off_t length, int blksize);
 void   vnode_pager_setsize (struct vnode *, vm_ooffset_t);
-int    nvtruncbuf (struct vnode *vp, off_t length, int blksize);
-void   nvnode_pager_setsize (struct vnode *vp, off_t length, int blksize);
+int    nvtruncbuf (struct vnode *vp, off_t length, int blksize, int boff);
 int    nvextendbuf(struct vnode *vp, off_t olength, off_t nlength,
-               int oblksize, int nblksize, int trivial);
+               int oblksize, int nblksize,
+               int oboff, int nboff, int trivial);
+void   nvnode_pager_setsize (struct vnode *vp, off_t length,
+               int blksize, int boff);
 int    vfsync(struct vnode *vp, int waitfor, int passes,
                int (*checkdef)(struct buf *),
                int (*waitoutput)(struct vnode *, struct thread *));
index 29cee4c..2c42905 100644 (file)
@@ -1250,10 +1250,11 @@ nfs_meta_setsize(struct vnode *vp, struct thread *td, off_t nsize, int trivial)
        np->n_size = nsize;
 
        if (nsize < osize) {
-               error = nvtruncbuf(vp, nsize, biosize);
+               error = nvtruncbuf(vp, nsize, biosize, -1);
        } else {
                error = nvextendbuf(vp, osize, nsize,
-                                   biosize, biosize, trivial);
+                                   biosize, biosize, -1, -1,
+                                   trivial);
        }
        return(error);
 }
index 48d4d56..498ed62 100644 (file)
@@ -848,7 +848,7 @@ nfs_loadattrcache(struct vnode *vp, struct mbuf **mdp, caddr_t *dposp,
                                np->n_size = vap->va_size;
                                np->n_flag |= NRMODIFIED;
                        }
-                       nvnode_pager_setsize(vp, np->n_size);
+                       nvnode_pager_setsize(vp, np->n_size, XXX);
 #endif
                } else {
                        np->n_size = vap->va_size;
index b845cbf..aa9bd5a 100644 (file)
@@ -201,23 +201,34 @@ ffs_truncate(struct vnode *vp, off_t length, int flags, struct ucred *cred)
 #endif
                        softdep_setup_freeblocks(oip, length);
                        vinvalbuf(ovp, 0, 0, 0);
-                       vnode_pager_setsize(ovp, 0);
+                       nvnode_pager_setsize(ovp, 0, fs->fs_bsize, 0);
                        oip->i_flag |= IN_CHANGE | IN_UPDATE;
                        return (ffs_update(ovp, 0));
                }
        }
        osize = oip->i_size;
+
        /*
         * Lengthen the size of the file. We must ensure that the
         * last byte of the file is allocated. Since the smallest
         * value of osize is 0, length will be at least 1.
+        *
+        * nvextendbuf() only breads the old buffer.  The blocksize
+        * of the new buffer must be specified so it knows how large
+        * to make the VM object.
         */
        if (osize < length) {
-               vnode_pager_setsize(ovp, length);
+               nvextendbuf(vp, osize, length,
+                           blkoffsize(fs, oip, osize), /* oblksize */
+                           blkoffresize(fs, length),   /* nblksize */
+                           blkoff(fs, osize),
+                           blkoff(fs, length),
+                           0);
+
                aflags = B_CLRBUF;
                if (flags & IO_SYNC)
                        aflags |= B_SYNC;
-               /* BALLOC reallocates fragment at old EOF */
+               /* BALLOC will reallocate the fragment at the old EOF */
                error = VOP_BALLOC(ovp, length - 1, 1, cred, aflags, &bp);
                if (error)
                        return (error);
@@ -231,23 +242,16 @@ ffs_truncate(struct vnode *vp, off_t length, int flags, struct ucred *cred)
                oip->i_flag |= IN_CHANGE | IN_UPDATE;
                return (ffs_update(ovp, 1));
        }
+
        /*
-        * Shorten the size of the file. If the file is not being
-        * truncated to a block boundary, the contents of the
-        * partial block following the end of the file must be
-        * zero'ed in case it ever becomes accessible again because
-        * of subsequent file growth. Directories however are not
-        * zero'ed as they should grow back initialized to empty.
+        * Shorten the size of the file.
         *
-        * The vtruncbuf() must be issued prior the b*write() of
-        * the buffer straddling the truncation point.  The b*write()
-        * calls vfs_clean_bio() which revalidates VM pages which
-        * may have been invalidated by the vtruncbuf().  Otherwise
-        * we may wind up with B_CACHE set on a buf containing invalid
-        * pages which will really mess up getpages.
+        * NOTE: The block size specified in nvtruncbuf() is the blocksize
+        *       of the buffer containing length prior to any reallocation
+        *       of the block.
         */
-       allerror = vtruncbuf(ovp, length, fs->fs_bsize);
-
+       allerror = nvtruncbuf(ovp, length, blkoffsize(fs, oip, length),
+                             blkoff(fs, length));
        offset = blkoff(fs, length);
        if (offset == 0) {
                oip->i_size = length;
@@ -260,6 +264,7 @@ ffs_truncate(struct vnode *vp, off_t length, int flags, struct ucred *cred)
                if (error) {
                        return (error);
                }
+
                /*
                 * When we are doing soft updates and the UFS_BALLOC
                 * above fills in a direct block hole with a full sized
@@ -275,9 +280,12 @@ ffs_truncate(struct vnode *vp, off_t length, int flags, struct ucred *cred)
                }
                oip->i_size = length;
                size = blksize(fs, oip, lbn);
+#if 0
+               /* vtruncbuf deals with this */
                if (ovp->v_type != VDIR)
                        bzero((char *)bp->b_data + offset,
                            (uint)(size - offset));
+#endif
                /* Kirk's code has reallocbuf(bp, size, 1) here */
                allocbuf(bp, size);
                if (bp->b_bufsize == fs->fs_bsize)
index b05e534..52ff517 100644 (file)
@@ -545,6 +545,12 @@ struct ocg {
          ? (fs)->fs_bsize \
          : (fragroundup(fs, blkoff(fs, (size)))))
 
+/*
+ * Extract the block size for the buffer cache buffer at offset (loc)
+ * relative to the current ip->i_size, or relative to a specific ip->i_size.
+ */
+#define blkoffsize(fs, ip, loc)        blksize(fs, ip, lblkno(fs, loc))
+#define blkoffresize(fs, loc) sblksize(fs, loc, lblkno(fs, loc))
 
 /*
  * Number of disk sectors per block/fragment; assumes DEV_BSIZE byte
index df7af7f..22fc72a 100644 (file)
@@ -716,7 +716,8 @@ ufs_direnter(struct vnode *dvp, struct vnode *tvp, struct direct *dirp,
                 */
                if (dp->i_offset & (DIRBLKSIZ - 1))
                        panic("ufs_direnter: newblk");
-               vnode_pager_setsize(dvp, dp->i_offset + DIRBLKSIZ);
+               nvnode_pager_setsize(dvp, dp->i_offset + DIRBLKSIZ,
+                                    DIRBLKSIZ, -1);
                flags = B_CLRBUF;
                if (!DOINGSOFTDEP(dvp) && !DOINGASYNC(dvp))
                        flags |= B_SYNC;
index c440239..cced082 100644 (file)
@@ -239,6 +239,7 @@ ffs_write(struct vop_write_args *ap)
        struct buf *bp;
        ufs_daddr_t lbn;
        off_t osize;
+       off_t nsize;
        int seqcount;
        int blkoffset, error, extended, flags, ioflag, resid, size, xfersize;
        struct thread *td;
@@ -312,8 +313,11 @@ ffs_write(struct vop_write_args *ap)
                if (uio->uio_resid < xfersize)
                        xfersize = uio->uio_resid;
 
-               if (uio->uio_offset + xfersize > ip->i_size)
-                       vnode_pager_setsize(vp, uio->uio_offset + xfersize);
+               if (uio->uio_offset + xfersize > ip->i_size) {
+                       nsize = uio->uio_offset + xfersize;
+                       nvnode_pager_setsize(vp, nsize,
+                               blkoffresize(fs, nsize), blkoff(fs, nsize));
+               }
 
 #if 0
                /*      
index 6a014a5..990b5a2 100644 (file)
@@ -1377,7 +1377,7 @@ ufs_mkdir(struct vop_old_mkdir_args *ap)
        dirtemplate = *dtp;
        dirtemplate.dot_ino = ip->i_number;
        dirtemplate.dotdot_ino = dp->i_number;
-       vnode_pager_setsize(tvp, DIRBLKSIZ);
+       nvnode_pager_setsize(tvp, DIRBLKSIZ, DIRBLKSIZ, -1);
        error = VOP_BALLOC(tvp, 0LL, DIRBLKSIZ, cnp->cn_cred, B_CLRBUF, &bp);
        if (error)
                goto bad;
index fe7601b..bfa9c23 100644 (file)
@@ -1159,12 +1159,11 @@ swap_pager_getpage(vm_object_t object, vm_page_t *mpp, int seqaccess)
         * set on the last page of the read-ahead to continue the pipeline.
         */
        if (mreq->valid == VM_PAGE_BITS_ALL) {
-               if (swap_burst_read)
+               if (swap_burst_read == 0 || mreq->pindex + 1 >= object->size)
                        return(VM_PAGER_OK);
                crit_enter();
-               blk = swp_pager_meta_ctl(mreq->object, mreq->pindex + 1, 0);
-               if (blk == SWAPBLK_NONE ||
-                   mreq->pindex + 1 >= object->size) {
+               blk = swp_pager_meta_ctl(object, mreq->pindex + 1, 0);
+               if (blk == SWAPBLK_NONE) {
                        crit_exit();
                        return(VM_PAGER_OK);
                }
@@ -1181,7 +1180,7 @@ swap_pager_getpage(vm_object_t object, vm_page_t *mpp, int seqaccess)
                                crit_exit();
                                return(VM_PAGER_OK);
                        }
-                       /*vm_page_unqueue_nowakeup(m);*/
+                       vm_page_unqueue_nowakeup(m);
                        vm_page_busy(m);
                }
                mreq = m;
@@ -1208,7 +1207,6 @@ swap_pager_getpage(vm_object_t object, vm_page_t *mpp, int seqaccess)
                    i < XIO_INTERNAL_PAGES &&
                    mreq->pindex + i < object->size; ++i) {
                daddr_t iblk;
-               break;
 
                iblk = swp_pager_meta_ctl(object, mreq->pindex + i, 0);
                if (iblk != blk + i)
@@ -1224,7 +1222,7 @@ swap_pager_getpage(vm_object_t object, vm_page_t *mpp, int seqaccess)
                } else {
                        if ((m->flags & PG_BUSY) || m->busy || m->valid)
                                break;
-                       /*vm_page_unqueue_nowakeup(m);*/
+                       vm_page_unqueue_nowakeup(m);
                        vm_page_busy(m);
                }
                marray[i] = m;
@@ -1285,7 +1283,7 @@ swap_pager_getpage(vm_object_t object, vm_page_t *mpp, int seqaccess)
         * We still hold the lock on mreq, and our automatic completion routine
         * does not remove it.
         */
-       vm_object_pip_add(mreq->object, bp->b_xio.xio_npages);
+       vm_object_pip_add(object, bp->b_xio.xio_npages);
 
        /*
         * perform the I/O.  NOTE!!!  bp cannot be considered valid after
@@ -1301,7 +1299,7 @@ swap_pager_getpage(vm_object_t object, vm_page_t *mpp, int seqaccess)
        vn_strategy(swapdev_vp, bio);
 
        /*
-        * wait for the page we want to complete.  PG_SWAPINPROG is always
+        * Wait for the page we want to complete.  PG_SWAPINPROG is always
         * cleared on completion.  If an I/O error occurs, SWAPBLK_NONE
         * is set in the meta-data.
         *
@@ -1311,8 +1309,10 @@ swap_pager_getpage(vm_object_t object, vm_page_t *mpp, int seqaccess)
        if (raonly)
                return(VM_PAGER_OK);
 
+       /*
+        * Read-ahead includes originally requested page case.
+        */
        crit_enter();
-
        while ((mreq->flags & PG_SWAPINPROG) != 0) {
                vm_page_flag_set(mreq, PG_WANTED | PG_REFERENCED);
                mycpu->gd_cnt.v_intrans++;
@@ -1325,7 +1325,6 @@ swap_pager_getpage(vm_object_t object, vm_page_t *mpp, int seqaccess)
                        );
                }
        }
-
        crit_exit();
 
        /*
index 67fad1c..d00d2e8 100644 (file)
@@ -1909,6 +1909,7 @@ vm_prefault(pmap_t pmap, vm_offset_t addra, vm_map_entry_t entry, int prot)
        crit_enter();
        for (i = 0; i < PAGEORDER_SIZE; i++) {
                vm_object_t lobject;
+               int allocated = 0;
 
                addr = addra + vm_prefault_pageorder[i];
                if (addr > addra + (PFFOR * PAGE_SIZE))
@@ -1928,6 +1929,10 @@ vm_prefault(pmap_t pmap, vm_offset_t addra, vm_map_entry_t entry, int prot)
                 * and we determine it would be advantageous, then allocate
                 * a zero-fill page for the base object.  The base object
                 * is guaranteed to be OBJT_DEFAULT for this case.
+                *
+                * In order to not have to check the pager via *haspage*()
+                * we stop if any non-default object is encountered.  e.g.
+                * a vnode or swap object would stop the loop.
                 */
                index = ((addr - entry->start) + entry->offset) >> PAGE_SHIFT;
                lobject = object;
@@ -1945,6 +1950,7 @@ vm_prefault(pmap_t pmap, vm_offset_t addra, vm_map_entry_t entry, int prot)
                                    vm_page_count_min(0)) {
                                        break;
                                }
+                               /* note: allocate from base object */
                                m = vm_page_alloc(object, index,
                                              VM_ALLOC_NORMAL | VM_ALLOC_ZERO);
 
@@ -1956,7 +1962,7 @@ vm_prefault(pmap_t pmap, vm_offset_t addra, vm_map_entry_t entry, int prot)
                                }
                                mycpu->gd_cnt.v_zfod++;
                                m->valid = VM_PAGE_BITS_ALL;
-                               vm_page_wakeup(m);
+                               allocated = 1;
                                pprot = prot;
                                /* lobject = object .. not needed */
                                break;
@@ -1993,9 +1999,16 @@ vm_prefault(pmap_t pmap, vm_offset_t addra, vm_map_entry_t entry, int prot)
                        vm_object_set_writeable_dirty(m->object);
 
                /*
-                * Enter the page into the pmap if appropriate.
+                * Enter the page into the pmap if appropriate.  If we had
+                * allocated the page we have to place it on a queue.  If not
+                * we just have to make sure it isn't on the cache queue
+                * (pages on the cache queue are not allowed to be mapped).
                 */
-               if (((m->valid & VM_PAGE_BITS_ALL) == VM_PAGE_BITS_ALL) &&
+               if (allocated) {
+                       pmap_enter(pmap, addr, m, pprot, 0);
+                       vm_page_deactivate(m);
+                       vm_page_wakeup(m);
+               } else if (((m->valid & VM_PAGE_BITS_ALL) == VM_PAGE_BITS_ALL) &&
                    (m->busy == 0) &&
                    (m->flags & (PG_BUSY | PG_FICTITIOUS)) == 0) {