[RFC 1/3] mm: process_mrelease: expedite clean file folio reclaim via mmu_gather

Minchan Kim
2026-04-13T22:40:02Z

Currently, process_mrelease() unmaps the pages but leaves clean file
folios on the LRU list, relying on standard memory reclaim to eventually
free them. This delays the immediate recovery of system memory under OOM
or container shutdown scenarios.

This patch implements an expedited eviction mechanism for clean file
folios by integrating directly into the low-level TLB batching
infrastructure (mmu_gather).

Instead of repeatedly locking and evicting folios one by one inside the
unmap loop (zap_present_folio_ptes), we pass the MMF_UNSTABLE flag
status down to free_pages_and_swap_cache(). Within this single unified
loop, anonymous pages are released via free_swap_cache(), and
file-backed folios are symmetrically truncated via mapping_evict_folio().

This avoids introducing unnecessary data structures, preserves TLB flush
safety, and removes duplicate tree traversals, resulting in an extremely
lean and highly responsive process_mrelease() implementation.

Signed-off-by: Minchan Kim <minchan@kernel.org>
---
arch/s390/include/asm/tlb.h | 2 +-
include/linux/swap.h | 9 ++++++---
mm/mmu_gather.c | 8 +++++---
mm/swap_state.c | 19 +++++++++++++++++--
4 files changed, 29 insertions(+), 9 deletions(-)

diff --git a/arch/s390/include/asm/tlb.h b/arch/s390/include/asm/tlb.h
index 619fd41e710e..554842345ccd 100644
--- a/arch/s390/include/asm/tlb.h
+++ b/arch/s390/include/asm/tlb.h
@@ -62,7 +62,7 @@ static inline bool __tlb_remove_folio_pages(struct mmu_gather *tlb,
VM_WARN_ON_ONCE(delay_rmap);
VM_WARN_ON_ONCE(page_folio(page) != page_folio(page + nr_pages - 1));

- free_pages_and_swap_cache(encoded_pages, ARRAY_SIZE(encoded_pages));
+ free_pages_and_caches(encoded_pages, ARRAY_SIZE(encoded_pages), false);
return false;
}

diff --git a/include/linux/swap.h b/include/linux/swap.h
index 62fc7499b408..e7b929b062f8 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -433,7 +433,7 @@ static inline unsigned long total_swapcache_pages(void)

void free_swap_cache(struct folio *folio);
void free_folio_and_swap_cache(struct folio *folio);
-void free_pages_and_swap_cache(struct encoded_page **, int);
+void free_pages_and_caches(struct encoded_page **pages, int nr, bool free_unmapped_file);
/* linux/mm/swapfile.c */
extern atomic_long_t nr_swap_pages;
extern long total_swap_pages;
@@ -510,8 +510,11 @@ static inline void put_swap_device(struct swap_info_struct *si)
do { (val)->freeswap = (val)->totalswap = 0; } while (0)
#define free_folio_and_swap_cache(folio) \
folio_put(folio)
-#define free_pages_and_swap_cache(pages, nr) \
- release_pages((pages), (nr));
+static inline void free_pages_and_caches(struct encoded_page **pages,
+ int nr, bool free_unmapped_file)
+{
+ release_pages(pages, nr);
+}

static inline void free_swap_cache(struct folio *folio)
{
diff --git a/mm/mmu_gather.c b/mm/mmu_gather.c
index fe5b6a031717..5ce5824db07f 100644
--- a/mm/mmu_gather.c
+++ b/mm/mmu_gather.c
@@ -100,7 +100,8 @@ void tlb_flush_rmaps(struct mmu_gather *tlb, struct vm_area_struct *vma)
*/
#define MAX_NR_FOLIOS_PER_FREE 512

-static void __tlb_batch_free_encoded_pages(struct mmu_gather_batch *batch)
+static void __tlb_batch_free_encoded_pages(struct mm_struct *mm,
+ struct mmu_gather_batch *batch)
{
struct encoded_page **pages = batch->encoded_pages;
unsigned int nr, nr_pages;
@@ -135,7 +136,8 @@ static void __tlb_batch_free_encoded_pages(struct mmu_gather_batch *batch)
}
}

- free_pages_and_swap_cache(pages, nr);
+ free_pages_and_caches(pages, nr,
+ mm_flags_test(MMF_UNSTABLE, mm));
pages += nr;
batch->nr -= nr;

@@ -148,7 +150,7 @@ static void tlb_batch_pages_flush(struct mmu_gather *tlb)
struct mmu_gather_batch *batch;

for (batch = &tlb->local; batch && batch->nr; batch = batch->next)
- __tlb_batch_free_encoded_pages(batch);
+ __tlb_batch_free_encoded_pages(tlb->mm, batch);
tlb->active = &tlb->local;
}

diff --git a/mm/swap_state.c b/mm/swap_state.c
index 6d0eef7470be..e70a52ead6d3 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -400,11 +400,22 @@ void free_folio_and_swap_cache(struct folio *folio)
folio_put(folio);
}

+static inline void free_file_cache(struct folio *folio)
+{
+ if (folio_trylock(folio)) {
+ mapping_evict_folio(folio_mapping(folio), folio);
+ folio_unlock(folio);
+ }
+}
+
/*
* Passed an array of pages, drop them all from swapcache and then release
* them. They are removed from the LRU and freed if this is their last use.
+ *
+ * If @free_unmapped_file is true, this function will proactively evict clean
+ * file-backed folios if they are no longer mapped.
*/
-void free_pages_and_swap_cache(struct encoded_page **pages, int nr)
+void free_pages_and_caches(struct encoded_page **pages, int nr, bool free_unmapped_file)
{
struct folio_batch folios;
unsigned int refs[PAGEVEC_SIZE];
@@ -413,7 +424,11 @@ void free_pages_and_swap_cache(struct encoded_page **pages, int nr)
for (int i = 0; i < nr; i++) {
struct folio *folio = page_folio(encoded_page_ptr(pages[i]));

- free_swap_cache(folio);
+ if (folio_test_anon(folio))
+ free_swap_cache(folio);
+ else if (unlikely(free_unmapped_file))
+ free_file_cache(folio);
+
refs[folios.nr] = 1;
if (unlikely(encoded_page_flags(pages[i]) &
ENCODED_PAGE_BIT_NR_PAGES_NEXT))
--
2.54.0.rc0.605.g598a273b03-goog