Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

f2fs: fix to add refcount once page is tagged PG_private

As Gao Xiang reported in bugzilla:

https://bugzilla.kernel.org/show_bug.cgi?id=202749

f2fs may skip pageout() due to incorrect page reference count.

The problem here is that MM defined the rule [1] very clearly that
once page was set with PG_private flag, we should increment the
refcount in that page, also main flows like pageout(), migrate_page()
will assume there is one additional page reference count if
page_has_private() returns true.

But currently, f2fs won't add/del refcount when changing PG_private
flag. Anyway, f2fs should follow MM's rule to make MM's related flows
running as expected.

[1] https://lore.kernel.org/lkml/2b19b3c4-2bc4-15fa-15cc-27a13e5c7af1@aol.com/

Reported-by: Gao Xiang <gaoxiang25@huawei.com>
Signed-off-by: Chao Yu <yuchao0@huawei.com>
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>

authored by

Chao Yu and committed by
Jaegeuk Kim
240a5915 25720cc0

+36 -23
+2 -2
fs/f2fs/checkpoint.c
··· 406 406 if (!PageDirty(page)) { 407 407 __set_page_dirty_nobuffers(page); 408 408 inc_page_count(F2FS_P_SB(page), F2FS_DIRTY_META); 409 - SetPagePrivate(page); 409 + f2fs_set_page_private(page, 0); 410 410 f2fs_trace_pid(page); 411 411 return 1; 412 412 } ··· 957 957 inode_inc_dirty_pages(inode); 958 958 spin_unlock(&sbi->inode_lock[type]); 959 959 960 - SetPagePrivate(page); 960 + f2fs_set_page_private(page, 0); 961 961 f2fs_trace_pid(page); 962 962 } 963 963
+8 -13
fs/f2fs/data.c
··· 2714 2714 if (IS_ATOMIC_WRITTEN_PAGE(page)) 2715 2715 return f2fs_drop_inmem_page(inode, page); 2716 2716 2717 - set_page_private(page, 0); 2718 - ClearPagePrivate(page); 2717 + f2fs_clear_page_private(page); 2719 2718 } 2720 2719 2721 2720 int f2fs_release_page(struct page *page, gfp_t wait) ··· 2728 2729 return 0; 2729 2730 2730 2731 clear_cold_data(page); 2731 - set_page_private(page, 0); 2732 - ClearPagePrivate(page); 2732 + f2fs_clear_page_private(page); 2733 2733 return 1; 2734 2734 } 2735 2735 ··· 2796 2798 return -EAGAIN; 2797 2799 } 2798 2800 2799 - /* 2800 - * A reference is expected if PagePrivate set when move mapping, 2801 - * however F2FS breaks this for maintaining dirty page counts when 2802 - * truncating pages. So here adjusting the 'extra_count' make it work. 2803 - */ 2804 - extra_count = (atomic_written ? 1 : 0) - page_has_private(page); 2801 + /* one extra reference was held for atomic_write page */ 2802 + extra_count = atomic_written ? 1 : 0; 2805 2803 rc = migrate_page_move_mapping(mapping, newpage, 2806 2804 page, mode, extra_count); 2807 2805 if (rc != MIGRATEPAGE_SUCCESS) { ··· 2818 2824 get_page(newpage); 2819 2825 } 2820 2826 2821 - if (PagePrivate(page)) 2822 - SetPagePrivate(newpage); 2823 - set_page_private(newpage, page_private(page)); 2827 + if (PagePrivate(page)) { 2828 + f2fs_set_page_private(newpage, page_private(page)); 2829 + f2fs_clear_page_private(page); 2830 + } 2824 2831 2825 2832 if (mode != MIGRATE_SYNC_NO_COPY) 2826 2833 migrate_page_copy(newpage, page);
+1 -1
fs/f2fs/dir.c
··· 728 728 !f2fs_truncate_hole(dir, page->index, page->index + 1)) { 729 729 f2fs_clear_page_cache_dirty_tag(page); 730 730 clear_page_dirty_for_io(page); 731 - ClearPagePrivate(page); 731 + f2fs_clear_page_private(page); 732 732 ClearPageUptodate(page); 733 733 clear_cold_data(page); 734 734 inode_dec_dirty_pages(dir);
+21
fs/f2fs/f2fs.h
··· 2835 2835 return true; 2836 2836 } 2837 2837 2838 + static inline void f2fs_set_page_private(struct page *page, 2839 + unsigned long data) 2840 + { 2841 + if (PagePrivate(page)) 2842 + return; 2843 + 2844 + get_page(page); 2845 + SetPagePrivate(page); 2846 + set_page_private(page, data); 2847 + } 2848 + 2849 + static inline void f2fs_clear_page_private(struct page *page) 2850 + { 2851 + if (!PagePrivate(page)) 2852 + return; 2853 + 2854 + set_page_private(page, 0); 2855 + ClearPagePrivate(page); 2856 + f2fs_put_page(page, 0); 2857 + } 2858 + 2838 2859 /* 2839 2860 * file.c 2840 2861 */
+1 -1
fs/f2fs/node.c
··· 1961 1961 if (!PageDirty(page)) { 1962 1962 __set_page_dirty_nobuffers(page); 1963 1963 inc_page_count(F2FS_P_SB(page), F2FS_DIRTY_NODES); 1964 - SetPagePrivate(page); 1964 + f2fs_set_page_private(page, 0); 1965 1965 f2fs_trace_pid(page); 1966 1966 return 1; 1967 1967 }
+3 -6
fs/f2fs/segment.c
··· 191 191 192 192 f2fs_trace_pid(page); 193 193 194 - set_page_private(page, (unsigned long)ATOMIC_WRITTEN_PAGE); 195 - SetPagePrivate(page); 194 + f2fs_set_page_private(page, (unsigned long)ATOMIC_WRITTEN_PAGE); 196 195 197 196 new = f2fs_kmem_cache_alloc(inmem_entry_slab, GFP_NOFS); 198 197 ··· 279 280 ClearPageUptodate(page); 280 281 clear_cold_data(page); 281 282 } 282 - set_page_private(page, 0); 283 - ClearPagePrivate(page); 283 + f2fs_clear_page_private(page); 284 284 f2fs_put_page(page, 1); 285 285 286 286 list_del(&cur->list); ··· 368 370 kmem_cache_free(inmem_entry_slab, cur); 369 371 370 372 ClearPageUptodate(page); 371 - set_page_private(page, 0); 372 - ClearPagePrivate(page); 373 + f2fs_clear_page_private(page); 373 374 f2fs_put_page(page, 0); 374 375 375 376 trace_f2fs_commit_inmem_page(page, INMEM_INVALIDATE);