Commit 79352894b28550ee0eee919149f57626ec1b3572
Committed by
Linus Torvalds
1 parent
83c54070ee
Exists in
master
and in
7 other branches
mm: fix clear_page_dirty_for_io vs fault race
Fix msync data loss and (less importantly) dirty page accounting inaccuracies due to the race remaining in clear_page_dirty_for_io(). The deleted comment explains what the race was, and the added comments explain how it is fixed. Signed-off-by: Nick Piggin <npiggin@suse.de> Acked-by: Linus Torvalds <torvalds@linux-foundation.org> Cc: Miklos Szeredi <miklos@szeredi.hu> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Showing 2 changed files with 21 additions and 5 deletions Side-by-side Diff
mm/memory.c
... | ... | @@ -1765,6 +1765,15 @@ |
1765 | 1765 | unlock: |
1766 | 1766 | pte_unmap_unlock(page_table, ptl); |
1767 | 1767 | if (dirty_page) { |
1768 | + /* | |
1769 | + * Yes, Virginia, this is actually required to prevent a race | |
1770 | + * with clear_page_dirty_for_io() from clearing the page dirty | |
1771 | + * bit after it clear all dirty ptes, but before a racing | |
1772 | + * do_wp_page installs a dirty pte. | |
1773 | + * | |
1774 | + * do_no_page is protected similarly. | |
1775 | + */ | |
1776 | + wait_on_page_locked(dirty_page); | |
1768 | 1777 | set_page_dirty_balance(dirty_page); |
1769 | 1778 | put_page(dirty_page); |
1770 | 1779 | } |
mm/page-writeback.c
... | ... | @@ -918,6 +918,8 @@ |
918 | 918 | { |
919 | 919 | struct address_space *mapping = page_mapping(page); |
920 | 920 | |
921 | + BUG_ON(!PageLocked(page)); | |
922 | + | |
921 | 923 | if (mapping && mapping_cap_account_dirty(mapping)) { |
922 | 924 | /* |
923 | 925 | * Yes, Virginia, this is indeed insane. |
924 | 926 | |
... | ... | @@ -943,14 +945,19 @@ |
943 | 945 | * We basically use the page "master dirty bit" |
944 | 946 | * as a serialization point for all the different |
945 | 947 | * threads doing their things. |
946 | - * | |
947 | - * FIXME! We still have a race here: if somebody | |
948 | - * adds the page back to the page tables in | |
949 | - * between the "page_mkclean()" and the "TestClearPageDirty()", | |
950 | - * we might have it mapped without the dirty bit set. | |
951 | 948 | */ |
952 | 949 | if (page_mkclean(page)) |
953 | 950 | set_page_dirty(page); |
951 | + /* | |
952 | + * We carefully synchronise fault handlers against | |
953 | + * installing a dirty pte and marking the page dirty | |
954 | + * at this point. We do this by having them hold the | |
955 | + * page lock at some point after installing their | |
956 | + * pte, but before marking the page dirty. | |
957 | + * Pages are always locked coming in here, so we get | |
958 | + * the desired exclusion. See mm/memory.c:do_wp_page() | |
959 | + * for more comments. | |
960 | + */ | |
954 | 961 | if (TestClearPageDirty(page)) { |
955 | 962 | dec_zone_page_state(page, NR_FILE_DIRTY); |
956 | 963 | return 1; |