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

From: Minchan Kim

Date: Tue Apr 14 2026 - 16:22:13 EST


On Tue, Apr 14, 2026 at 09:45:42AM +0200, David Hildenbrand (Arm) wrote:
> On 4/14/26 00:39, Minchan Kim wrote:
> > 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.
>
> process_mrelease() calls __oom_reap_task_mm().
>
> There, we skip any MAP_SHARED file mappings.
>
> So I assume what you describe only applies to MAP_PRIVATE file mappings?
> What about MAP_SHARED?

That's true. My primary target was MAP_PRIVATE because MAP_SHARED pages
are often not exclusive to the target process.

>
> Also "leaves ... on the LRU list" is rather confusing. They are not
> evicted and stay in the pagecache?

Yes, they are not evicted and remain in the pagecache, which is the
problem this patch addresses.

>
> >
> > This patch implements an expedited eviction mechanism for clean file
> > folios by integrating directly into the low-level TLB batching
> > infrastructure (mmu_gather).
>
> Is this a complicated way of saying "Handle clean pagecache folios
> similar to swapcache folios in mmu_gather code, dropping them from the
> swapcache (i.e., evicting them) if they are completely unmapped during
> reaping"?

Much better description. Thanks.

>
> >
> > 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().
>
> ... where you still evict them one-by-one. Rather confusing.

I initially considered implementing this within zap_present_folio_ptes,
but concluded that mmu_gather is the appropriate place. To avoid
confusion, I will remove the "Instead of...unmap_loop" line in the next
revision.

>
> >
> > 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.
>
> I don't think this paragraph adds a lot of value, really.
>
> Which "duplicate tree traversal"? Which unnecessary data structures?

I had considered gathering these folios into a separate data structure
for cleanup, but realized mmu_gather is the best place to avoid
duplicate traversals for both anon and file folios.

>
> Is that AI generated text? A lot of the stuff here reads AI generated. I
> yet have to meet a developer (not a sales person) that would just say
> "extremely lean and highly responsive process_mrelease() implementation"

That's too business words, I agree. Let me removing them.

>
> If it is AI generated, throw it away and write it yourself from scratch.
> Use AI only to polish your English.
>
> >
> > Signed-off-by: Minchan Kim <minchan@xxxxxxxxxx>
> > ---
> > 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);
>
> As we dislike boolean parameters, we either try to avoid them (e.g., use
> flags) or document the parameters using something like

I like your suggestion "try_evict_file_folios"

>
> "/* parameter_name= */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);
> > +}
>
> Why should !CONFIG_SWAP not take care of free_unmapped_file?

There is no reason to exclude it bust just missed this one.
I will make sure to address this in the next respin.

>
>
> >
> > 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.
>
> The parameter name is not really expressive.
>
> You are not freeing unmapped files.
>
> "try_evict_file_folios" maybe?

+1

Thanks.