History

fs: introduce some page/buffer invariants

It is a bug to set a page dirty if it is not uptodate unless it has buffers. If the page has buffers, then the page may be dirty (some buffers dirty) but not uptodate (some buffers not uptodate). The exception to this rule is if the set_page_dirty caller is racing with truncate or invalidate. A buffer can not be set dirty if it is not uptodate. If either of these situations occurs, it indicates there could be some data loss problem. Some of these warnings could be a harmless one where the page or buffer is set uptodate immediately after it is dirtied, however we should fix those up, and enforce this ordering. Bring the order of operations for truncate into line with those of invalidate. This will prevent a page from being able to go !uptodate while we're holding the tree_lock, which is probably a good thing anyway.

Nick Piggin committed 19 years ago

Commit 787d2211 parent a1ed3dd3 files · +39 −18Browse files

Unified · Split

3 files
  1. fs/buffer.c +37 −17
  2. mm/page-writeback.c +1 −0
  3. mm/truncate.c +1 −1
fs/buffer.c+37−17
@@ -675,6 +675,39 @@ void mark_buffer_dirty_inode(struct buffer_head *bh, struct inode *inode) · 674 unmodified lines
675675 }
676676 EXPORT_SYMBOL(mark_buffer_dirty_inode);
677677
678+/*
679+ * Mark the page dirty, and set it dirty in the radix tree, and mark the inode
680+ * dirty.
681+ *
682+ * If warn is true, then emit a warning if the page is not uptodate and has
683+ * not been truncated.
684+ */
685+static int __set_page_dirty(struct page *page,
686+ struct address_space *mapping, int warn)
687+{
688+ if (unlikely(!mapping))
689+ return !TestSetPageDirty(page);
690+
691+ if (TestSetPageDirty(page))
692+ return 0;
693+
694+ write_lock_irq(&mapping->tree_lock);
695+ if (page->mapping) { /* Race with truncate? */
696+ WARN_ON_ONCE(warn && !PageUptodate(page));
697+
698+ if (mapping_cap_account_dirty(mapping)) {
699+ __inc_zone_page_state(page, NR_FILE_DIRTY);
700+ task_io_account_write(PAGE_CACHE_SIZE);
701+ }
702+ radix_tree_tag_set(&mapping->page_tree,
703+ page_index(page), PAGECACHE_TAG_DIRTY);
704+ }
705+ write_unlock_irq(&mapping->tree_lock);
706+ __mark_inode_dirty(mapping->host, I_DIRTY_PAGES);
707+
708+ return 1;
709+}
710+
678711 /*
679712 * Add a page to the dirty page list.
680713 *
@@ -702,7 +735,7 @@ EXPORT_SYMBOL(mark_buffer_dirty_inode); · 21 unmodified lines
702735 */
703736 int __set_page_dirty_buffers(struct page *page)
704737 {
705− struct address_space * const mapping = page_mapping(page);
738+ struct address_space *mapping = page_mapping(page);
706739
707740 if (unlikely(!mapping))
708741 return !TestSetPageDirty(page);
@@ -719,21 +752,7 @@ int __set_page_dirty_buffers(struct page *page) · 10 unmodified lines
719752 }
720753 spin_unlock(&mapping->private_lock);
721754
722− if (TestSetPageDirty(page))
723− return 0;
724−
725− write_lock_irq(&mapping->tree_lock);
726− if (page->mapping) { /* Race with truncate? */
727− if (mapping_cap_account_dirty(mapping)) {
728− __inc_zone_page_state(page, NR_FILE_DIRTY);
729− task_io_account_write(PAGE_CACHE_SIZE);
730− }
731− radix_tree_tag_set(&mapping->page_tree,
732− page_index(page), PAGECACHE_TAG_DIRTY);
733− }
734− write_unlock_irq(&mapping->tree_lock);
735− __mark_inode_dirty(mapping->host, I_DIRTY_PAGES);
736− return 1;
755+ return __set_page_dirty(page, mapping, 1);
737756 }
738757 EXPORT_SYMBOL(__set_page_dirty_buffers);
739758
@@ -1132,8 +1151,9 @@ __getblk_slow(struct block_device *bdev, sector_t block, int size) · 392 unmodified lines
11321151 */
11331152 void fastcall mark_buffer_dirty(struct buffer_head *bh)
11341153 {
1154+ WARN_ON_ONCE(!buffer_uptodate(bh));
11351155 if (!buffer_dirty(bh) && !test_set_buffer_dirty(bh))
1136− __set_page_dirty_nobuffers(bh->b_page);
1156+ __set_page_dirty(bh->b_page, page_mapping(bh->b_page), 0);
11371157 }
11381158
11391159 /*
mm/page-writeback.c+1−0
@@ -824,6 +824,7 @@ int __set_page_dirty_nobuffers(struct page *page) · 823 unmodified lines
824824 mapping2 = page_mapping(page);
825825 if (mapping2) { /* Race with truncate? */
826826 BUG_ON(mapping2 != mapping);
827+ WARN_ON_ONCE(!PagePrivate(page) && !PageUptodate(page));
827828 if (mapping_cap_account_dirty(mapping)) {
828829 __inc_zone_page_state(page, NR_FILE_DIRTY);
829830 task_io_account_write(PAGE_CACHE_SIZE);
mm/truncate.c+1−1
@@ -100,9 +100,9 @@ truncate_complete_page(struct address_space *mapping, struct page *page) · 99 unmodified lines
100100 if (PagePrivate(page))
101101 do_invalidatepage(page, 0);
102102
103+ remove_from_page_cache(page);
103104 ClearPageUptodate(page);
104105 ClearPageMappedToDisk(page);
105− remove_from_page_cache(page);
106106 page_cache_release(page); /* pagecache ref */
107107 }
108108