Skip to content

Commit 0c27140

Browse files
koct9isfrothwell
authored andcommitted
page_writeback: clean up mess around cancel_dirty_page()
This patch replaces cancel_dirty_page() with helper account_page_cleaned() which only updates counters. It's called from truncate_complete_page() and from try_to_free_buffers() (hack for ext3). Page is locked in both cases, page-lock protects against concurrent dirtiers: see commit 2d6d7f9 ("mm: protect set_page_dirty() from ongoing truncation"). Delete_from_page_cache() shouldn't be called for dirty pages, they must be handled by caller (either written or truncated). This patch treats final dirty accounting fixup at the end of __delete_from_page_cache() as a debug check and adds WARN_ON_ONCE() around it. If something removes dirty pages without proper handling that might be a bug and unwritten data might be lost. Hugetlbfs has no dirty pages accounting, ClearPageDirty() is enough here. cancel_dirty_page() in nfs_wb_page_cancel() is redundant. This is helper for nfs_invalidate_page() and it's called only in case complete invalidation. The mess was started in v2.6.20 after commits 46d2277 ("Clean up and make try_to_free_buffers() not race with dirty pages") and 3e67c09 ("truncate: clear page dirtiness before running try_to_free_buffers()") first was reverted right in v2.6.20 in commit ecdfc97 ("Resurrect 'try_to_free_buffers()' VM hackery"), second in v2.6.25 commit a2b3456 ("Fix dirty page accounting leak with ext3 data=journal"). Custom fixes were introduced between these points. NFS in v2.6.23, commit 1b3b4a1 ("NFS: Fix a write request leak in nfs_invalidate_page()"). Kludge in __delete_from_page_cache() in v2.6.24, commit 3a69279 ("Do dirty page accounting when removing a page from the page cache"). Since v2.6.25 all of them are redundant. Signed-off-by: Konstantin Khlebnikov <[email protected]> Cc: Tejun Heo <[email protected]> Cc: Jan Kara <[email protected]> Signed-off-by: Andrew Morton <[email protected]>
1 parent 5ac922e commit 0c27140

File tree

9 files changed

+41
-49
lines changed

9 files changed

+41
-49
lines changed

Diff for: drivers/staging/lustre/lustre/include/linux/lustre_patchless_compat.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ truncate_complete_page(struct address_space *mapping, struct page *page)
5555
if (PagePrivate(page))
5656
page->mapping->a_ops->invalidatepage(page, 0, PAGE_CACHE_SIZE);
5757

58-
cancel_dirty_page(page, PAGE_SIZE);
58+
if (TestClearPageDirty(page))
59+
account_page_cleaned(page, mapping);
60+
5961
ClearPageMappedToDisk(page);
6062
ll_delete_from_page_cache(page);
6163
}

Diff for: fs/buffer.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -3243,8 +3243,8 @@ int try_to_free_buffers(struct page *page)
32433243
* to synchronise against __set_page_dirty_buffers and prevent the
32443244
* dirty bit from being lost.
32453245
*/
3246-
if (ret)
3247-
cancel_dirty_page(page, PAGE_CACHE_SIZE);
3246+
if (ret && TestClearPageDirty(page))
3247+
account_page_cleaned(page, mapping);
32483248
spin_unlock(&mapping->private_lock);
32493249
out:
32503250
if (buffers_to_free) {

Diff for: fs/hugetlbfs/inode.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ static int hugetlbfs_write_end(struct file *file, struct address_space *mapping,
319319

320320
static void truncate_huge_page(struct page *page)
321321
{
322-
cancel_dirty_page(page, /* No IO accounting for huge pages? */0);
322+
ClearPageDirty(page);
323323
ClearPageUptodate(page);
324324
delete_from_page_cache(page);
325325
}

Diff for: fs/nfs/write.c

-5
Original file line numberDiff line numberDiff line change
@@ -1876,11 +1876,6 @@ int nfs_wb_page_cancel(struct inode *inode, struct page *page)
18761876
* request from the inode / page_private pointer and
18771877
* release it */
18781878
nfs_inode_remove_request(req);
1879-
/*
1880-
* In case nfs_inode_remove_request has marked the
1881-
* page as being dirty
1882-
*/
1883-
cancel_dirty_page(page, PAGE_CACHE_SIZE);
18841879
nfs_unlock_and_release_request(req);
18851880
}
18861881

Diff for: include/linux/mm.h

+2
Original file line numberDiff line numberDiff line change
@@ -1294,9 +1294,11 @@ int __set_page_dirty_no_writeback(struct page *page);
12941294
int redirty_page_for_writepage(struct writeback_control *wbc,
12951295
struct page *page);
12961296
void account_page_dirtied(struct page *page, struct address_space *mapping);
1297+
void account_page_cleaned(struct page *page, struct address_space *mapping);
12971298
int set_page_dirty(struct page *page);
12981299
int set_page_dirty_lock(struct page *page);
12991300
int clear_page_dirty_for_io(struct page *page);
1301+
13001302
int get_cmdline(struct task_struct *task, char *buffer, int buflen);
13011303

13021304
/* Is the vma a continuation of the stack vma above it? */

Diff for: include/linux/page-flags.h

-2
Original file line numberDiff line numberDiff line change
@@ -328,8 +328,6 @@ static inline void SetPageUptodate(struct page *page)
328328

329329
CLEARPAGEFLAG(Uptodate, uptodate)
330330

331-
extern void cancel_dirty_page(struct page *page, unsigned int account_size);
332-
333331
int test_clear_page_writeback(struct page *page);
334332
int __test_set_page_writeback(struct page *page, bool keep_write);
335333

Diff for: mm/filemap.c

+7-8
Original file line numberDiff line numberDiff line change
@@ -203,16 +203,15 @@ void __delete_from_page_cache(struct page *page, void *shadow)
203203
BUG_ON(page_mapped(page));
204204

205205
/*
206-
* Some filesystems seem to re-dirty the page even after
207-
* the VM has canceled the dirty bit (eg ext3 journaling).
206+
* At this point page must be either written or cleaned by truncate.
207+
* Dirty page here signals about bug and loosing unwitten data.
208208
*
209-
* Fix it up by doing a final dirty accounting check after
210-
* having removed the page entirely.
209+
* This fixes dirty accounting after removing the page entirely but
210+
* leaves PageDirty set: it has no effect for truncated page and
211+
* anyway will be cleared before returning page into buddy allocator.
211212
*/
212-
if (PageDirty(page) && mapping_cap_account_dirty(mapping)) {
213-
dec_zone_page_state(page, NR_FILE_DIRTY);
214-
dec_bdi_stat(inode_to_bdi(mapping->host), BDI_RECLAIMABLE);
215-
}
213+
if (WARN_ON_ONCE(PageDirty(page)))
214+
account_page_cleaned(page, mapping);
216215
}
217216

218217
/**

Diff for: mm/page-writeback.c

+19
Original file line numberDiff line numberDiff line change
@@ -2110,6 +2110,25 @@ void account_page_dirtied(struct page *page, struct address_space *mapping)
21102110
}
21112111
EXPORT_SYMBOL(account_page_dirtied);
21122112

2113+
/*
2114+
* Helper function for deaccounting dirty page without writeback.
2115+
*
2116+
* Doing this should *normally* only ever be done when a page
2117+
* is truncated, and is not actually mapped anywhere at all. However,
2118+
* fs/buffer.c does this when it notices that somebody has cleaned
2119+
* out all the buffers on a page without actually doing it through
2120+
* the VM. Can you say "ext3 is horribly ugly"? Thought you could.
2121+
*/
2122+
void account_page_cleaned(struct page *page, struct address_space *mapping)
2123+
{
2124+
if (mapping_cap_account_dirty(mapping)) {
2125+
dec_zone_page_state(page, NR_FILE_DIRTY);
2126+
dec_bdi_stat(inode_to_bdi(mapping->host), BDI_RECLAIMABLE);
2127+
task_io_account_cancelled_write(PAGE_CACHE_SIZE);
2128+
}
2129+
}
2130+
EXPORT_SYMBOL(account_page_cleaned);
2131+
21132132
/*
21142133
* For address_spaces which do not use buffers. Just tag the page as dirty in
21152134
* its radix tree.

Diff for: mm/truncate.c

+7-30
Original file line numberDiff line numberDiff line change
@@ -92,35 +92,6 @@ void do_invalidatepage(struct page *page, unsigned int offset,
9292
(*invalidatepage)(page, offset, length);
9393
}
9494

95-
/*
96-
* This cancels just the dirty bit on the kernel page itself, it
97-
* does NOT actually remove dirty bits on any mmap's that may be
98-
* around. It also leaves the page tagged dirty, so any sync
99-
* activity will still find it on the dirty lists, and in particular,
100-
* clear_page_dirty_for_io() will still look at the dirty bits in
101-
* the VM.
102-
*
103-
* Doing this should *normally* only ever be done when a page
104-
* is truncated, and is not actually mapped anywhere at all. However,
105-
* fs/buffer.c does this when it notices that somebody has cleaned
106-
* out all the buffers on a page without actually doing it through
107-
* the VM. Can you say "ext3 is horribly ugly"? Tought you could.
108-
*/
109-
void cancel_dirty_page(struct page *page, unsigned int account_size)
110-
{
111-
if (TestClearPageDirty(page)) {
112-
struct address_space *mapping = page->mapping;
113-
if (mapping && mapping_cap_account_dirty(mapping)) {
114-
dec_zone_page_state(page, NR_FILE_DIRTY);
115-
dec_bdi_stat(inode_to_bdi(mapping->host),
116-
BDI_RECLAIMABLE);
117-
if (account_size)
118-
task_io_account_cancelled_write(account_size);
119-
}
120-
}
121-
}
122-
EXPORT_SYMBOL(cancel_dirty_page);
123-
12495
/*
12596
* If truncate cannot remove the fs-private metadata from the page, the page
12697
* becomes orphaned. It will be left on the LRU and may even be mapped into
@@ -140,7 +111,13 @@ truncate_complete_page(struct address_space *mapping, struct page *page)
140111
if (page_has_private(page))
141112
do_invalidatepage(page, 0, PAGE_CACHE_SIZE);
142113

143-
cancel_dirty_page(page, PAGE_CACHE_SIZE);
114+
/*
115+
* Some filesystems seem to re-dirty the page even after
116+
* the VM has canceled the dirty bit (eg ext3 journaling).
117+
* Hence dirty accounting check is placed after invalidation.
118+
*/
119+
if (TestClearPageDirty(page))
120+
account_page_cleaned(page, mapping);
144121

145122
ClearPageMappedToDisk(page);
146123
delete_from_page_cache(page);

0 commit comments

Comments
 (0)