Add filemap_dirty_folio_pages() which is equivalent to filemap_dirty_folio() except it takes in the number of pages in the folio to account for as dirty when it updates internal dirty stats instead of accounting all pages in the folio as dirty. If the folio is already dirty, calling this function will still update the stats. As such, the caller is responsible for ensuring no overaccounting happens. The same caller responsibilities apply here as for filemap_dirty_folio() (eg, should ensure this doesn't race with truncation/writeback). Signed-off-by: Joanne Koong --- fs/buffer.c | 4 ++-- include/linux/pagemap.h | 2 +- include/linux/writeback.h | 2 ++ mm/page-writeback.c | 41 +++++++++++++++++++++++++++++++++++---- 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/fs/buffer.c b/fs/buffer.c index 65c96c432800..558591254fdb 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -752,7 +752,7 @@ bool block_dirty_folio(struct address_space *mapping, struct folio *folio) if (newly_dirty) __folio_mark_dirty(folio, mapping, 1, - folio_nr_pages(folio)); + folio_nr_pages(folio), true); if (newly_dirty) __mark_inode_dirty(mapping->host, I_DIRTY_PAGES); @@ -1205,7 +1205,7 @@ void mark_buffer_dirty(struct buffer_head *bh) mapping = folio->mapping; if (mapping) __folio_mark_dirty(folio, mapping, 0, - folio_nr_pages(folio)); + folio_nr_pages(folio), true); } if (mapping) __mark_inode_dirty(mapping->host, I_DIRTY_PAGES); diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h index 48745f8f6dfe..510bc6e0f70b 100644 --- a/include/linux/pagemap.h +++ b/include/linux/pagemap.h @@ -1224,7 +1224,7 @@ void folio_end_writeback(struct folio *folio); void folio_end_writeback_pages(struct folio *folio, long nr_pages); void folio_wait_stable(struct folio *folio); void __folio_mark_dirty(struct folio *folio, struct address_space *, int warn, - long nr_pages); + long nr_pages, bool newly_dirty); void folio_account_cleaned(struct folio *folio, struct bdi_writeback *wb); void __folio_cancel_dirty(struct folio *folio); static inline void folio_cancel_dirty(struct folio *folio) diff --git a/include/linux/writeback.h b/include/linux/writeback.h index a2848d731a46..0df11d00cce2 100644 --- a/include/linux/writeback.h +++ b/include/linux/writeback.h @@ -372,6 +372,8 @@ void tag_pages_for_writeback(struct address_space *mapping, pgoff_t start, pgoff_t end); bool filemap_dirty_folio(struct address_space *mapping, struct folio *folio); +bool filemap_dirty_folio_pages(struct address_space *mapping, + struct folio *folio, long nr_pages); bool folio_redirty_for_writepage(struct writeback_control *, struct folio *); bool redirty_page_for_writepage(struct writeback_control *, struct page *); diff --git a/mm/page-writeback.c b/mm/page-writeback.c index e66eef2d1584..1f862ab3c68d 100644 --- a/mm/page-writeback.c +++ b/mm/page-writeback.c @@ -2730,7 +2730,7 @@ void folio_account_cleaned(struct folio *folio, struct bdi_writeback *wb) * try_to_free_buffers() to fail. */ void __folio_mark_dirty(struct folio *folio, struct address_space *mapping, - int warn, long nr_pages) + int warn, long nr_pages, bool newly_dirty) { unsigned long flags; @@ -2738,8 +2738,9 @@ void __folio_mark_dirty(struct folio *folio, struct address_space *mapping, if (folio->mapping) { /* Race with truncate? */ WARN_ON_ONCE(warn && !folio_test_uptodate(folio)); folio_account_dirtied(folio, mapping, nr_pages); - __xa_set_mark(&mapping->i_pages, folio_index(folio), - PAGECACHE_TAG_DIRTY); + if (newly_dirty) + __xa_set_mark(&mapping->i_pages, folio_index(folio), + PAGECACHE_TAG_DIRTY); } xa_unlock_irqrestore(&mapping->i_pages, flags); } @@ -2769,7 +2770,7 @@ bool filemap_dirty_folio(struct address_space *mapping, struct folio *folio) return false; __folio_mark_dirty(folio, mapping, !folio_test_private(folio), - folio_nr_pages(folio)); + folio_nr_pages(folio), true); if (mapping->host) { /* !PageAnon && !swapper_space */ @@ -2779,6 +2780,38 @@ bool filemap_dirty_folio(struct address_space *mapping, struct folio *folio) } EXPORT_SYMBOL(filemap_dirty_folio); +/** + * filemap_dirty_folio_pages - Mark a folio dirty and update stats to account + * for dirtying @nr_pages within the folio. + * @mapping: Address space this folio belongs to. + * @folio: Folio to be marked as dirty. + * @nr_pages: Number of pages to dirty. + * + * This is equivalent to filemap_dirty_folio() except it takes in the number of + * pages in the folio to account for as dirty when it updates internal dirty + * stats instead of accounting all pages in the folio as dirty. If the folio is + * already dirty, calling this function will still update the stats. As such, + * the caller is responsible for ensuring no overaccounting happens. + * + * The same caller responsibilities apply here as for filemap_dirty_folio() + * (eg, should ensure this doesn't race with truncation/writeback). + */ +bool filemap_dirty_folio_pages(struct address_space *mapping, + struct folio *folio, long nr_pages) +{ + bool newly_dirty = !folio_test_set_dirty(folio); + + __folio_mark_dirty(folio, mapping, !folio_test_private(folio), + nr_pages, newly_dirty); + + if (newly_dirty && mapping->host) { + /* !PageAnon && !swapper_space */ + __mark_inode_dirty(mapping->host, I_DIRTY_PAGES); + } + + return newly_dirty; +} + /** * folio_redirty_for_writepage - Decline to write a dirty folio. * @wbc: The writeback control. -- 2.47.3