[PATCH v6 1/2] mm: Merge folio_has_private()/filemap_release_folio() call pairs

From: David Howells
Date: Thu Feb 16 2023 - 10:08:49 EST


Make filemap_release_folio() check folio_has_private(). Then, in most
cases, where a call to folio_has_private() is immediately followed by a
call to filemap_release_folio(), we can get rid of the test in the pair.

There are a couple of sites in mm/vscan.c that this can't so easily be
done. In shrink_folio_list(), there are actually three cases (something
different is done for incompletely invalidated buffers), but
filemap_release_folio() elides two of them.

In shrink_active_list(), we don't have have the folio lock yet, so the
check allows us to avoid locking the page unnecessarily.

A wrapper function to check if a folio needs release is provided for those
places that still need to do it in the mm/ directory. This will acquire
additional parts to the condition in a future patch.

After this, the only remaining caller of folio_has_private() outside of mm/
is a check in fuse.

Reported-by: Rohith Surabattula <rohiths.msft@xxxxxxxxx>
Suggested-by: Matthew Wilcox <willy@xxxxxxxxxxxxx>
Signed-off-by: David Howells <dhowells@xxxxxxxxxx>
cc: Matthew Wilcox <willy@xxxxxxxxxxxxx>
cc: Linus Torvalds <torvalds@xxxxxxxxxxxxxxxxxxxx>
cc: Steve French <sfrench@xxxxxxxxx>
cc: Shyam Prasad N <nspmangalore@xxxxxxxxx>
cc: Rohith Surabattula <rohiths.msft@xxxxxxxxx>
cc: Dave Wysochanski <dwysocha@xxxxxxxxxx>
cc: Dominique Martinet <asmadeus@xxxxxxxxxxxxx>
cc: Ilya Dryomov <idryomov@xxxxxxxxx>
cc: "Theodore Ts'o" <tytso@xxxxxxx>
cc: Andreas Dilger <adilger.kernel@xxxxxxxxx>
cc: linux-cachefs@xxxxxxxxxx
cc: linux-cifs@xxxxxxxxxxxxxxx
cc: linux-afs@xxxxxxxxxxxxxxxxxxx
cc: v9fs-developer@xxxxxxxxxxxxxxxxxxxxx
cc: ceph-devel@xxxxxxxxxxxxxxx
cc: linux-nfs@xxxxxxxxxxxxxxx
cc: linux-ext4@xxxxxxxxxxxxxxx
cc: linux-fsdevel@xxxxxxxxxxxxxxx
cc: linux-mm@xxxxxxxxx
---

Notes:
ver #5)
- Rebased on linus/master. try_to_release_page() has now been entirely
replaced by filemap_release_folio(), barring one comment.
- Cleaned up some pairs in ext4.

ver #4)
- Split from fscache fix.
- Moved folio_needs_release() to mm/internal.h and removed open-coded
version from filemap_release_folio().

ver #3)
- Fixed mapping_clear_release_always() to use clear_bit() not set_bit().
- Moved a '&&' to the correct line.

ver #2)
- Rewrote entirely according to Willy's suggestion[1].

fs/ext4/move_extent.c | 12 ++++--------
fs/splice.c | 3 +--
mm/filemap.c | 2 ++
mm/huge_memory.c | 3 +--
mm/internal.h | 8 ++++++++
mm/khugepaged.c | 3 +--
mm/memory-failure.c | 8 +++-----
mm/migrate.c | 3 +--
mm/truncate.c | 6 ++----
mm/vmscan.c | 8 ++++----
10 files changed, 27 insertions(+), 29 deletions(-)

diff --git a/fs/ext4/move_extent.c b/fs/ext4/move_extent.c
index 8dbb87edf24c..dedc9d445f24 100644
--- a/fs/ext4/move_extent.c
+++ b/fs/ext4/move_extent.c
@@ -339,10 +339,8 @@ move_extent_per_page(struct file *o_filp, struct inode *donor_inode,
ext4_double_up_write_data_sem(orig_inode, donor_inode);
goto data_copy;
}
- if ((folio_has_private(folio[0]) &&
- !filemap_release_folio(folio[0], 0)) ||
- (folio_has_private(folio[1]) &&
- !filemap_release_folio(folio[1], 0))) {
+ if (!filemap_release_folio(folio[0], 0) ||
+ !filemap_release_folio(folio[1], 0)) {
*err = -EBUSY;
goto drop_data_sem;
}
@@ -361,10 +359,8 @@ move_extent_per_page(struct file *o_filp, struct inode *donor_inode,

/* At this point all buffers in range are uptodate, old mapping layout
* is no longer required, try to drop it now. */
- if ((folio_has_private(folio[0]) &&
- !filemap_release_folio(folio[0], 0)) ||
- (folio_has_private(folio[1]) &&
- !filemap_release_folio(folio[1], 0))) {
+ if (!filemap_release_folio(folio[0], 0) ||
+ !filemap_release_folio(folio[1], 0)) {
*err = -EBUSY;
goto unlock_folios;
}
diff --git a/fs/splice.c b/fs/splice.c
index 5969b7a1d353..e69eddaf9d7c 100644
--- a/fs/splice.c
+++ b/fs/splice.c
@@ -65,8 +65,7 @@ static bool page_cache_pipe_buf_try_steal(struct pipe_inode_info *pipe,
*/
folio_wait_writeback(folio);

- if (folio_has_private(folio) &&
- !filemap_release_folio(folio, GFP_KERNEL))
+ if (!filemap_release_folio(folio, GFP_KERNEL))
goto out_unlock;

/*
diff --git a/mm/filemap.c b/mm/filemap.c
index c4d4ace9cc70..344146c170b0 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -3960,6 +3960,8 @@ bool filemap_release_folio(struct folio *folio, gfp_t gfp)
struct address_space * const mapping = folio->mapping;

BUG_ON(!folio_test_locked(folio));
+ if (!folio_needs_release(folio))
+ return true;
if (folio_test_writeback(folio))
return false;

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index abe6cfd92ffa..8490c42dedb3 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -2702,8 +2702,7 @@ int split_huge_page_to_list(struct page *page, struct list_head *list)
gfp = current_gfp_context(mapping_gfp_mask(mapping) &
GFP_RECLAIM_MASK);

- if (folio_test_private(folio) &&
- !filemap_release_folio(folio, gfp)) {
+ if (!filemap_release_folio(folio, gfp)) {
ret = -EBUSY;
goto out;
}
diff --git a/mm/internal.h b/mm/internal.h
index bcf75a8b032d..c4c8e58e1d12 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -163,6 +163,14 @@ static inline void set_page_refcounted(struct page *page)
set_page_count(page, 1);
}

+/*
+ * Return true if a folio needs ->release_folio() calling upon it.
+ */
+static inline bool folio_needs_release(struct folio *folio)
+{
+ return folio_has_private(folio);
+}
+
extern unsigned long highest_memmap_pfn;

/*
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index 90acfea40c13..e257e0a13ad1 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -1944,8 +1944,7 @@ static int collapse_file(struct mm_struct *mm, unsigned long addr,
goto out_unlock;
}

- if (folio_has_private(folio) &&
- !filemap_release_folio(folio, GFP_KERNEL)) {
+ if (!filemap_release_folio(folio, GFP_KERNEL)) {
result = SCAN_PAGE_HAS_PRIVATE;
folio_putback_lru(folio);
goto out_unlock;
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index c77a9e37e27e..a4f809c11ae9 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -843,14 +843,12 @@ static int truncate_error_page(struct page *p, unsigned long pfn,
struct folio *folio = page_folio(p);
int err = mapping->a_ops->error_remove_page(mapping, p);

- if (err != 0) {
+ if (err != 0)
pr_info("%#lx: Failed to punch page: %d\n", pfn, err);
- } else if (folio_has_private(folio) &&
- !filemap_release_folio(folio, GFP_NOIO)) {
+ else if (!filemap_release_folio(folio, GFP_NOIO))
pr_info("%#lx: failed to release buffers\n", pfn);
- } else {
+ else
ret = MF_RECOVERED;
- }
} else {
/*
* If the file system doesn't support it just invalidate
diff --git a/mm/migrate.c b/mm/migrate.c
index a4d3fc65085f..db867bb80128 100644
--- a/mm/migrate.c
+++ b/mm/migrate.c
@@ -915,8 +915,7 @@ static int fallback_migrate_folio(struct address_space *mapping,
* Buffers may be managed in a filesystem specific way.
* We must have no buffers or drop them.
*/
- if (folio_test_private(src) &&
- !filemap_release_folio(src, GFP_KERNEL))
+ if (!filemap_release_folio(src, GFP_KERNEL))
return mode == MIGRATE_SYNC ? -EAGAIN : -EBUSY;

return migrate_folio(mapping, dst, src, mode);
diff --git a/mm/truncate.c b/mm/truncate.c
index 7b4ea4c4a46b..8378aabb5294 100644
--- a/mm/truncate.c
+++ b/mm/truncate.c
@@ -19,7 +19,6 @@
#include <linux/highmem.h>
#include <linux/pagevec.h>
#include <linux/task_io_accounting_ops.h>
-#include <linux/buffer_head.h> /* grr. try_to_release_page */
#include <linux/shmem_fs.h>
#include <linux/rmap.h>
#include "internal.h"
@@ -276,7 +275,7 @@ static long mapping_evict_folio(struct address_space *mapping,
if (folio_ref_count(folio) >
folio_nr_pages(folio) + folio_has_private(folio) + 1)
return 0;
- if (folio_has_private(folio) && !filemap_release_folio(folio, 0))
+ if (!filemap_release_folio(folio, 0))
return 0;

return remove_mapping(mapping, folio);
@@ -573,8 +572,7 @@ static int invalidate_complete_folio2(struct address_space *mapping,
if (folio->mapping != mapping)
return 0;

- if (folio_has_private(folio) &&
- !filemap_release_folio(folio, GFP_KERNEL))
+ if (!filemap_release_folio(folio, GFP_KERNEL))
return 0;

spin_lock(&mapping->host->i_lock);
diff --git a/mm/vmscan.c b/mm/vmscan.c
index bf3eedf0209c..0fd6623adccb 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -1996,7 +1996,7 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,
* (refcount == 1) it can be freed. Otherwise, leave
* the folio on the LRU so it is swappable.
*/
- if (folio_has_private(folio)) {
+ if (folio_needs_release(folio)) {
if (!filemap_release_folio(folio, sc->gfp_mask))
goto activate_locked;
if (!mapping && folio_ref_count(folio) == 1) {
@@ -2641,9 +2641,9 @@ static void shrink_active_list(unsigned long nr_to_scan,
}

if (unlikely(buffer_heads_over_limit)) {
- if (folio_test_private(folio) && folio_trylock(folio)) {
- if (folio_test_private(folio))
- filemap_release_folio(folio, 0);
+ if (folio_needs_release(folio) &&
+ folio_trylock(folio)) {
+ filemap_release_folio(folio, 0);
folio_unlock(folio);
}
}