Skip to content

Commit 073861e

Browse files
Hugh Dickinstorvalds
authored andcommitted
mm: fix VM_BUG_ON(PageTail) and BUG_ON(PageWriteback)
Twice now, when exercising ext4 looped on shmem huge pages, I have crashed on the PF_ONLY_HEAD check inside PageWaiters(): ext4_finish_bio() calling end_page_writeback() calling wake_up_page() on tail of a shmem huge page, no longer an ext4 page at all. The problem is that PageWriteback is not accompanied by a page reference (as the NOTE at the end of test_clear_page_writeback() acknowledges): as soon as TestClearPageWriteback has been done, that page could be removed from page cache, freed, and reused for something else by the time that wake_up_page() is reached. https://lore.kernel.org/linux-mm/[email protected]/ Matthew Wilcox suggested avoiding or weakening the PageWaiters() tail check; but I'm paranoid about even looking at an unreferenced struct page, lest its memory might itself have already been reused or hotremoved (and wake_up_page_bit() may modify that memory with its ClearPageWaiters()). Then on crashing a second time, realized there's a stronger reason against that approach. If my testing just occasionally crashes on that check, when the page is reused for part of a compound page, wouldn't it be much more common for the page to get reused as an order-0 page before reaching wake_up_page()? And on rare occasions, might that reused page already be marked PageWriteback by its new user, and already be waited upon? What would that look like? It would look like BUG_ON(PageWriteback) after wait_on_page_writeback() in write_cache_pages() (though I have never seen that crash myself). Matthew Wilcox explaining this to himself: "page is allocated, added to page cache, dirtied, writeback starts, --- thread A --- filesystem calls end_page_writeback() test_clear_page_writeback() --- context switch to thread B --- truncate_inode_pages_range() finds the page, it doesn't have writeback set, we delete it from the page cache. Page gets reallocated, dirtied, writeback starts again. Then we call write_cache_pages(), see PageWriteback() set, call wait_on_page_writeback() --- context switch back to thread A --- wake_up_page(page, PG_writeback); ... thread B is woken, but because the wakeup was for the old use of the page, PageWriteback is still set. Devious" And prior to 2a9127f ("mm: rewrite wait_on_page_bit_common() logic") this would have been much less likely: before that, wake_page_function()'s non-exclusive case would stop walking and not wake if it found Writeback already set again; whereas now the non-exclusive case proceeds to wake. I have not thought of a fix that does not add a little overhead: the simplest fix is for end_page_writeback() to get_page() before calling test_clear_page_writeback(), then put_page() after wake_up_page(). Was there a chance of missed wakeups before, since a page freed before reaching wake_up_page() would have PageWaiters cleared? I think not, because each waiter does hold a reference on the page. This bug comes when the old use of the page, the one we do TestClearPageWriteback on, had *no* waiters, so no additional page reference beyond the page cache (and whoever racily freed it). The reuse of the page has a waiter holding a reference, and its own PageWriteback set; but the belated wake_up_page() has woken the reuse to hit that BUG_ON(PageWriteback). Reported-by: [email protected] Reported-by: Qian Cai <[email protected]> Fixes: 2a9127f ("mm: rewrite wait_on_page_bit_common() logic") Signed-off-by: Hugh Dickins <[email protected]> Cc: [email protected] # v5.8+ Signed-off-by: Linus Torvalds <[email protected]>
1 parent 80145ac commit 073861e

File tree

2 files changed

+8
-6
lines changed

2 files changed

+8
-6
lines changed

mm/filemap.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1484,11 +1484,19 @@ void end_page_writeback(struct page *page)
14841484
rotate_reclaimable_page(page);
14851485
}
14861486

1487+
/*
1488+
* Writeback does not hold a page reference of its own, relying
1489+
* on truncation to wait for the clearing of PG_writeback.
1490+
* But here we must make sure that the page is not freed and
1491+
* reused before the wake_up_page().
1492+
*/
1493+
get_page(page);
14871494
if (!test_clear_page_writeback(page))
14881495
BUG();
14891496

14901497
smp_mb__after_atomic();
14911498
wake_up_page(page, PG_writeback);
1499+
put_page(page);
14921500
}
14931501
EXPORT_SYMBOL(end_page_writeback);
14941502

mm/page-writeback.c

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2754,12 +2754,6 @@ int test_clear_page_writeback(struct page *page)
27542754
} else {
27552755
ret = TestClearPageWriteback(page);
27562756
}
2757-
/*
2758-
* NOTE: Page might be free now! Writeback doesn't hold a page
2759-
* reference on its own, it relies on truncation to wait for
2760-
* the clearing of PG_writeback. The below can only access
2761-
* page state that is static across allocation cycles.
2762-
*/
27632757
if (ret) {
27642758
dec_lruvec_state(lruvec, NR_WRITEBACK);
27652759
dec_zone_page_state(page, NR_ZONE_WRITE_PENDING);

0 commit comments

Comments
 (0)