Gitweb:     
http://git.kernel.org/git/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=7658cc289288b8ae7dd2c2224549a048431222b3
Commit:     7658cc289288b8ae7dd2c2224549a048431222b3
Parent:     3bf8ba38f38d3647368e4edcf7d019f9f8d9184a
Author:     Linus Torvalds <[EMAIL PROTECTED]>
AuthorDate: Fri Dec 29 10:00:58 2006 -0800
Committer:  Linus Torvalds <[EMAIL PROTECTED]>
CommitDate: Fri Dec 29 10:00:58 2006 -0800

    VM: Fix nasty and subtle race in shared mmap'ed page writeback
    
    The VM layer (on the face of it, fairly reasonably) expected that when
    it does a ->writepage() call to the filesystem, it would write out the
    full page at that point in time.  Especially since it had earlier marked
    the whole page dirty with "set_page_dirty()".
    
    But that isn't actually the case: ->writepage() does not actually write
    a page, it writes the parts of the page that have been explicitly marked
    dirty before, *and* that had not got written out for other reasons since
    the last time we told it they were dirty.
    
    That last caveat is the important one.
    
    Which _most_ of the time ends up being the whole page (since we had
    called "set_page_dirty()" on the page earlier), but if the filesystem
    had done any dirty flushing of its own (for example, to honor some
    internal write ordering guarantees), it might end up doing only a
    partial page IO (or none at all) when ->writepage() is actually called.
    
    That is the correct thing in general (since we actually often _want_
    only the known-dirty parts of the page to be written out), but the
    shared dirty page handling had implicitly forgotten about these details,
    and had a number of cases where it was doing just the "->writepage()"
    part, without telling the low-level filesystem that the whole page might
    have been re-dirtied as part of being mapped writably into user space.
    
    Since most of the time the FS did actually write out the full page, we
    didn't notice this for a loong time, and this needed some really odd
    patterns to trigger.  But it caused occasional corruption with rtorrent
    and with the Debian "apt" database, because both use shared mmaps to
    update the end result.
    
    This fixes it. Finally. After way too much hair-pulling.
    
    Acked-by: Nick Piggin <[EMAIL PROTECTED]>
    Acked-by: Martin J. Bligh <[EMAIL PROTECTED]>
    Acked-by: Martin Michlmayr <[EMAIL PROTECTED]>
    Acked-by: Martin Johansson <[EMAIL PROTECTED]>
    Acked-by: Ingo Molnar <[EMAIL PROTECTED]>
    Acked-by: Andrei Popa <[EMAIL PROTECTED]>
    Cc: High Dickins <[EMAIL PROTECTED]>
    Cc: Andrew Morton <[EMAIL PROTECTED]>,
    Cc: Peter Zijlstra <[EMAIL PROTECTED]>
    Cc: Segher Boessenkool <[EMAIL PROTECTED]>
    Cc: David Miller <[EMAIL PROTECTED]>
    Cc: Arjan van de Ven <[EMAIL PROTECTED]>
    Cc: Gordon Farquharson <[EMAIL PROTECTED]>
    Cc: Guillaume Chazarain <[EMAIL PROTECTED]>
    Cc: Theodore Tso <[EMAIL PROTECTED]>
    Cc: Kenneth Cheng <[EMAIL PROTECTED]>
    Cc: Tobias Diedrich <[EMAIL PROTECTED]>
    Signed-off-by: Linus Torvalds <[EMAIL PROTECTED]>
---
 mm/page-writeback.c |   45 +++++++++++++++++++++++++++++++++++++--------
 1 files changed, 37 insertions(+), 8 deletions(-)

diff --git a/mm/page-writeback.c b/mm/page-writeback.c
index b3a198c..1d2fc89 100644
--- a/mm/page-writeback.c
+++ b/mm/page-writeback.c
@@ -862,17 +862,46 @@ int clear_page_dirty_for_io(struct page *page)
 {
        struct address_space *mapping = page_mapping(page);
 
-       if (!mapping)
-               return TestClearPageDirty(page);
-
-       if (TestClearPageDirty(page)) {
-               if (mapping_cap_account_dirty(mapping)) {
-                       page_mkclean(page);
+       if (mapping && mapping_cap_account_dirty(mapping)) {
+               /*
+                * Yes, Virginia, this is indeed insane.
+                *
+                * We use this sequence to make sure that
+                *  (a) we account for dirty stats properly
+                *  (b) we tell the low-level filesystem to
+                *      mark the whole page dirty if it was
+                *      dirty in a pagetable. Only to then
+                *  (c) clean the page again and return 1 to
+                *      cause the writeback.
+                *
+                * This way we avoid all nasty races with the
+                * dirty bit in multiple places and clearing
+                * them concurrently from different threads.
+                *
+                * Note! Normally the "set_page_dirty(page)"
+                * has no effect on the actual dirty bit - since
+                * that will already usually be set. But we
+                * need the side effects, and it can help us
+                * avoid races.
+                *
+                * We basically use the page "master dirty bit"
+                * as a serialization point for all the different
+                * threads doing their things.
+                *
+                * FIXME! We still have a race here: if somebody
+                * adds the page back to the page tables in
+                * between the "page_mkclean()" and the "TestClearPageDirty()",
+                * we might have it mapped without the dirty bit set.
+                */
+               if (page_mkclean(page))
+                       set_page_dirty(page);
+               if (TestClearPageDirty(page)) {
                        dec_zone_page_state(page, NR_FILE_DIRTY);
+                       return 1;
                }
-               return 1;
+               return 0;
        }
-       return 0;
+       return TestClearPageDirty(page);
 }
 EXPORT_SYMBOL(clear_page_dirty_for_io);
 
-
To unsubscribe from this list: send the line "unsubscribe git-commits-head" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to