Kernel - Close VM/BIO races and document.o
authorMatthew Dillon <dillon@apollo.backplane.com>
Fri, 28 Aug 2009 03:34:50 +0000 (20:34 -0700)
committerMatthew Dillon <dillon@apollo.backplane.com>
Fri, 28 Aug 2009 05:41:10 +0000 (22:41 -0700)
commitcb1cf930f3044653f7c85caa21cec345878b00f1
tree565396a16730c1528773f7e88cc185836421d9aa
parent48df63eb1cd333a08ce3b6da0d0adb12e53924ac
Kernel - Close VM/BIO races and document.o

* Remove vfs_setdirty(), it is no longer used.
  Remove vfs_page_set_valid(), it is no longer used.
  Remove vfs_bio_set_valid(), it is no longer used.

* When acquiring a buffer with getblk() whos size differs from the
  buffer already cached, no longer destroy the VM pages backing
  the buffer after completing the write.  Instead just release
  the buffer so a new, larger one can be constructed.

  NFS buffers which straddle file EOF can remain cached after the
  file has been extended via seek/write or ftruncate, and their
  underlying VM pages may become dirty via mmap.  If the buffer
  is acquired later the underlying VM pages beyond the buffer's
  original b_bcount size must be retained, not destroyed.

* No longer try to clear the pmap modified bit from misc vm_page_*()
  functions.  In cases where we desire the pmap modified bit to be
  clear, it should *already* have been cleared in the run-up to the
  I/O.  Clearing it later may cause the buffer cache to lose track
  of the fact that underlying VM pages may have been modified again.

  NFS buffers use b_dirtyoff/b_dirtyend to determine what to actually
  write.  If the VM page is modified again the current write operation
  will not cover all the dirty parts of the buffer and another write
  will have to be issued.  Clearing the pmap modified bit at later
  stages did not properly track changes in b_dirtyoff/b_dirtyend and
  resulted in dirty data being lost.

* Implement vfs_clean_one_page() to deal with nearly all buffer cache vs
  backing VM page dirty->clean handling at the appropriate time.

  In addition, this function now detects the case where a buffer has
  B_NEEDCOMMIT set but the underlying VM page is dirty.  This
  function necessarily only clears the dirty bits associated
  with the buffer because buffer sizes are not necessarily page aligned,
  which is different from clearing ALL the dirty bits as the putpages
  code is able to do.  So the B_NEEDCOMMIT test is only against those
  dirty bits associated with the buffer.  If this is found to be the
  case the B_NEEDCOMMIT flag is cleared.

  This fixes a race where VM pages backing a dirty buffer which has gone
  through the phase-1 commit are dirtied via a mmap, and NFS then goes
  through with the phase-2 commit and throws the data away when it really
  needed to go back and do another phase-1 commit.

* In vnode_generic_put_pages() no longer clear the VM page dirty bits
  associated with bits of a file which extend past file EOF in the
  page straddling the EOF.  We used to do this with the idea that
  we would only clear the dirty bits up to the file EOF later on
  in the I/O completion code.

  However, this was too fragile.  If a page ended up with any dirty
  bits left set it would remain endless dirty and be reflushed forever.

  We now clear the dirty bits for the entire page after a putpages
  operation completes without error, and don't bother doing it
  prior to I/O initiation.

* Call nfs_meta_setsize() for both seek+write extensions (holes) and for
  ftruncate extensions (holes).

  nfs_meta_setsize() now deterministically adjusts the size of the buffer
  that was straddling the PRIOR EOF point, fixing an issue where
  write-extending a file to near the end of a nfs buffer boundary (32K),
  then seek-write extending it further by creating a hole, then
  mmap()ing the end of the first chunk and modifying data past the
  original write-extend point... would lose the extra data because
  the original buffer was still intact and was still sized for the
  original EOF.  This was difficult to reproduce because it only occurred
  if all the dirty bits got cleared when the original buffer is flushed,
  meaning the original write-extend point had to be within 511 bytes of
  the end of a 32K boundary.
sys/kern/vfs_bio.c
sys/vfs/devfs/devfs_vnops.c
sys/vfs/nfs/nfs.h
sys/vfs/nfs/nfs_bio.c
sys/vfs/nfs/nfs_vnops.c
sys/vfs/nwfs/nwfs_io.c
sys/vfs/smbfs/smbfs_io.c
sys/vm/vm_page.c
sys/vm/vm_page2.h
sys/vm/vnode_pager.c