From: Hugh Dickins <hughd@google.com>
To: "Vlastimil Babka (SUSE)" <vbabka@kernel.org>
Cc: Hugh Dickins <hughd@google.com>,
Andrew Morton <akpm@linux-foundation.org>,
Ackerley Tng <ackerleytng@google.com>,
Alexander Viro <viro@zeniv.linux.org.uk>,
Alexandre Ghiti <alex@ghiti.fr>,
Baolin Wang <baolin.wang@linux.alibaba.com>,
Barry Song <baohua@kernel.org>,
Binbin Wu <binbin.wu@linux.intel.com>,
Christian Brauner <brauner@kernel.org>,
Christoph Hellwig <hch@lst.de>,
Christoph Lameter <cl@gentwo.org>,
Claudio Imbrenda <imbrenda@linux.ibm.com>,
David Hildenbrand <david@kernel.org>,
JP Kobryn <jp.kobryn@linux.dev>, Jan Kara <jack@suse.cz>,
Jens Axboe <axboe@kernel.dk>,
Johannes Weiner <hannes@cmpxchg.org>,
Kairui Song <ryncsn@gmail.com>, Kiryl Shutsemau <kas@kernel.org>,
Lance Yang <lance.yang@linux.dev>,
Leonardo Bras <leobras.c@gmail.com>,
Lorenzo Stoakes <ljs@kernel.org>,
Marcelo Tosatti <mtosatti@redhat.com>,
Matthew Wilcox <willy@infradead.org>,
Mel Gorman <mgorman@techsingularity.net>,
Miaohe Lin <linmiaohe@huawei.com>,
Michal Hocko <mhocko@suse.com>, Minchan Kim <minchan@kernel.org>,
Muchun Song <muchun.song@linux.dev>,
Oscar Salvador <osalvador@suse.de>,
Peter Zijlstra <peterz@infradead.org>,
Qi Zheng <qi.zheng@linux.dev>, Rik van Riel <riel@surriel.com>,
Sebastian Andrzej Siewior <bigeasy@linutronix.de>,
Shakeel Butt <shakeel.butt@linux.dev>,
Suren Baghdasaryan <surenb@google.com>,
Yang Shi <yang@os.amperecomputing.com>,
Yu Zhao <yuzhao@google.com>, Zach O'Keefe <zokeefe@google.com>,
Zi Yan <ziy@nvidia.com>,
linux-block@vger.kernel.org, linux-fsdevel@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-mm@kvack.org
Subject: Re: [PATCH v2 04/26] mm/fbatch: lru bit set, no extra ref, while folio on per-cpu fbatch
Date: Sat, 12 Sep 2026 12:30:55 -0700 (PDT) [thread overview]
Message-ID: <3f712e41-78a8-5622-67dc-f462d2c97d11@google.com> (raw)
In-Reply-To: <296bf543-23e0-417a-a729-c222392a1777@kernel.org>
On Wed, 9 Sep 2026, Vlastimil Babka (SUSE) wrote:
> On 9/9/26 11:49, Hugh Dickins wrote:
> > Treat folios on a per-cpu fbatch as if they were already on the lruvec:
> > with PG_lru set, without holding an extra reference. This will enable
> > the removal of most lru_add_drain() and lru_add_drain_all() calls soon.
> >
> > Recognize such a folio by 0x02 set in the folio->lru.next pointer by
> > folio_add_lru(). Then lruvec_del_folio() (aided by "lru_add_del_folio")
>
> I admit I find the name lru_add_del_folio() quite confusing. What it does is
> AFAIU try to delete from a folio_batch, which is not necessarily the lru_add
> one?
It is to delete from the lru_add fbatch: that's the one where a folio
is put into the LRU system initially, and it's the one which has this
special LRU_NEXT_BATCHED linkage to the fbatch, not any proper next,prev
linkage into a real LRU list.
Which is not to say that that folio could not also occur in some of the
other fbatches (for this or other CPUs) at the same time: it might,
because of the un-refcoount-raised folio getting freed and reused;
and it might, as the same instance of the folio, be acted upon by one
of the other LRU manipulation functions. Those latter tend to have
"!folio_test_lru" checks in, but now the lru_add ones do have lru set;
they tend to be reached through a real LRU list of folios (e.g. deciding
to deactivate something found on an active list), but I don't think that's
necessarily the case.
As to be name: I did try out several alternatives, but was most satisfied
by "lru_add_del_folio". I'll certainly grant that it's amusing, and maybe
too confusing if it were to be a widely-used API; but it mostly ends up
just nestling inside lruvec_del_folio().
> I don't have an alternative proposal ready though, naming is hard.
>
> > can pretend to unlink it, and folio_batch_move_lru()'s lru_add case can
> > check whether one of the others has already moved it to lruvec.
> >
> > (That bit is also used in a transient way by set_page_pfmemalloc(), to
> > inform interested callers whether page_is_pfmemalloc(): but those callers
> > are in networking, not putting folios on LRU; and accept that any use of
> > the page->lru field already erases page_is_pfmemalloc() information.)
> >
> > Let folio->lru.next point to the lru_add fbatch entry, but this is now
> > just for debugging: it seemed to be important for folio_batch_move_lru()
> > to distinguish fresh from stale entries, but then it turned out that it
> > has to processs them identically.
> >
> > Activate, deactivates and move_tail, holding no reference on the folio,
> > might come to act on a stale folio when the fbatch is drained: but it's
> > acquired by try_get and test_clear_lru, so safe even when suboptimal.
> >
> > Reclaim is not an exact science, and there have been no complaints of
> > missed actions since 5.11 commit fc574c23558c ("mm/swap.c: serialize
> > memcg changes in pagevec_lru_move_fn") introduced the TestClearPageLRU
> > protocol: so don't expect complaints of a few surprisingly taken actions.
> >
> > Signed-off-by: Hugh Dickins <hughd@google.com>
>
> Tricky, and I guess my questions below will betray I didn't grasp all the
> nuances...
Many thanks for looking: it is the core, and the hardest to review.
>
> > ---
> > include/linux/mm_inline.h | 25 ++++++++
> > include/linux/mm_types.h | 6 +-
> > mm/folio.c | 119 +++++++++++++-------------------------
> > mm/huge_memory.c | 6 +-
> > 4 files changed, 74 insertions(+), 82 deletions(-)
> >
> > diff --git a/include/linux/mm_inline.h b/include/linux/mm_inline.h
> > index 621c8653d8f7..8420b1276535 100644
> > --- a/include/linux/mm_inline.h
> > +++ b/include/linux/mm_inline.h
> > @@ -343,6 +343,29 @@ static inline void folio_migrate_refs(struct folio *new, const struct folio *old
> > }
> > #endif /* CONFIG_LRU_GEN */
> >
> > +enum {
> > + LRU_NEXT_NEVER_TAIL = 0, /* Used by a tail's compound_head */
> > + LRU_NEXT_BATCHED = 1, /* Not used by any aligned pointer */
> > + NR_LRU_NEXT_FLAGS
> > +};
> > +
> > +static __always_inline
> > +bool lru_add_del_folio(struct folio *folio)
> > +{
> > + unsigned long lru_next = READ_ONCE(folio->lru_next);
> > +
> > + /* BUG_ON(folio_test_lru(folio) && folio_ref_count(folio)); */
>
> Maybe do it as VM_WARN_ON_ONCE() and then it's acceptable for mainline and
> gets excercised by bots?
All I want there is the comment; but I thought the most concise and
helpful way to write the comment would be to write it as a BUG_ON().
I'm not keen on bloating inlines with assertions, and _ONCEs have
their cost on top - all mitigated by the VM_, true. I can change
it if asked, but all I want to put there myself is the comment.
>
> > + if (!(lru_next & BIT(LRU_NEXT_BATCHED)))
> > + return false;
> > +
> > + WRITE_ONCE(folio->lru.next, LIST_POISON1);
> > + /* BUG_ON(folio->lru_next & BIT(LRU_NEXT_BATCHED)); */
>
> Is this a test for unexpected LIST_POISON1 definitions (in which case
> BUILD_BUG_ON would work?) or a test for a race that should not happen,
> however with a very tiny detection window?
Again, it's just a comment, that things will go wrong if someone one
day decides to redefine LIST_POISON1 in a way which conflicts. Yes,
if it were uncommented, then I'd prefer to put a BUILD_BUG_ON instead.
> > --- a/mm/folio.c
> > +++ b/mm/folio.c
> > @@ -152,57 +152,34 @@ static void folio_batch_move_lru(struct folio_batch *fbatch, move_fn_t move_fn)
> > int i;
> > struct lruvec *lruvec = NULL;
> > unsigned long flags = 0;
> > - struct folio_batch free_fbatch;
> > - bool is_lru_add = (move_fn == lru_add);
> > -
> > - /*
> > - * If we're adding to the LRU, preemptively filter dead folios. Use
> > - * this dedicated folio batch for temp storage and deferred cleanup.
> > - */
> > - if (is_lru_add)
> > - folio_batch_init(&free_fbatch);
> >
> > for (i = 0; i < folio_batch_count(fbatch); i++) {
> > struct folio *folio = fbatch->folios[i];
> >
> > - /* block memcg migration while the folio moves between lru */
> > - if (!is_lru_add && !folio_test_clear_lru(folio))
> > - continue;
> > -
> > - /*
> > - * Filter dead folios by moving them from the add batch to the temp
> > - * batch for freeing after this loop.
> > - *
> > - * We're bypassing normal cleanup. Clear flags that are not
> > - * applicable to dead folios.
> > - *
> > - * Since the folio may be part of a huge page, unqueue from
> > - * deferred split list to avoid a dangling list entry.
> > - */
> > - if (is_lru_add && folio_ref_freeze(folio, 1)) {
> > - __folio_clear_active(folio);
> > - __folio_clear_unevictable(folio);
> > - folio_unqueue_deferred_split(folio);
> > + if (!folio_try_get(folio)) {
> > fbatch->folios[i] = NULL;
> > - folio_batch_add(&free_fbatch, folio);
> > continue;
> > }
> >
> > + if (!folio_test_clear_lru(folio))
> > + continue;
> > +
> > + /* Do not add to LRU if it has already been added */
> > + if (move_fn == lru_add && !lru_add_del_folio(folio))
> > + goto restore_lru;
> > +
> > folio_lruvec_relock_irqsave(folio, &lruvec, &flags);
> > move_fn(lruvec, folio);
> >
> > + /* Do add to LRU if not already there (move_fn skipped) */
> > + if (lru_add_del_folio(folio))
> > + lruvec_add_folio(lruvec, folio);
>
> AFAICS the previous code didn't do anything like this (and move_fn could
> skip or not all the same?) , and I wonder if it's now (sorry) load-bearing,
> or an optimization?
Good question, an optimization or essential? I'd have to ask for more
time, to give you a definitive answer on that. What I can easily do is
give the thinking behind it. We're the lucky one who got to clear lru
bit, it's conceivable that a racing task (particularly or necessarily?
when the folio has got freed and reused meanwhile) wanting to put this
folio on real LRU failed to clear lru bit and so skipped this entry,
and in that case it is our responsibilty to do so (usually the move_fn
does an lruvec del and add which accomplishes that, but not when it
skipped the folio as having unsuitable flags).
Those lines were not in my initial attempt, and I never noticed any
badness from not having them (folio unreclaimable and unmigratable
until freed, I presume); but when working on the mlock+munlock I
became more aware of this need for the lru-bit-clearer to do the
work others are expecting. (And I do not pretend that munlock is
complete in that respect yet: no more imperfect than before,
I think, but more work to do if perfection is needed.)
> > diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> > index afbb5974bd22..c7510d875433 100644
> > --- a/mm/huge_memory.c
> > +++ b/mm/huge_memory.c
> > @@ -3995,8 +3995,12 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
> > }
> >
> > /* lock lru list/PageCompound, ref frozen by page_ref_freeze */
> > - if (do_lru)
> > + if (do_lru) {
> > lruvec = folio_lruvec_lock(folio);
> > + /* Move from fbatch to lruvec before lru_add_split_folio()s */
> > + if (lru_add_del_folio(folio))
> > + lruvec_add_folio(lruvec, folio);
>
> This is not mentioned in the changelog. Why is it necessary now?
>
> > + }
> >
> > ret = __split_unmapped_folio(folio, new_order, split_at, xas,
> > mapping, split_type);
It caught me by surprise too,
I was very lucky that our internal testing crashed without that, it's a
narrow race when we're not batching large folios. But you only have to
look at lru_add_split_folio() to see, that it does (very reasonably)
expect a folio with the lru bit set to have sensible next,prev.
(WHereas most others isolate a folio before working on it, hugepage
splitting is peculiar in having a mode where folio is left on lru,
but frozen to refcount zero while the work is done).
Thanks! More replies later...
Hugh
next prev parent reply other threads:[~2026-09-12 19:31 UTC|newest]
Thread overview: 40+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-09 9:39 [PATCH v2 00/26] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
2026-09-09 9:42 ` [PATCH v2 01/26] mm/fbatch: remove !CONFIG_SMP special case of folio_activate() Hugh Dickins
2026-09-09 9:44 ` [PATCH v2 02/26] mm/fbatch: allow folios_put_refs() to skip xa_is_value() entries Hugh Dickins
2026-09-09 9:46 ` [PATCH v2 03/26] mm/fbatch: temporarily disable lazyfree and mlock+munlock batching Hugh Dickins
2026-09-09 9:49 ` [PATCH v2 04/26] mm/fbatch: lru bit set, no extra ref, while folio on per-cpu fbatch Hugh Dickins
2026-09-09 12:25 ` Vlastimil Babka (SUSE)
2026-09-12 19:30 ` Hugh Dickins [this message]
2026-09-09 9:51 ` [PATCH v2 05/26] mm/fbatch: lru_add_del_folio()+folio_add_lru() after clear_lru() Hugh Dickins
2026-09-09 15:04 ` Vlastimil Babka (SUSE)
2026-09-12 21:59 ` Hugh Dickins
2026-09-09 9:53 ` [PATCH v2 06/26] mm/fbatch: fbatch_drain_lazyfree(onstack fbatch) before ptl unlock Hugh Dickins
2026-09-09 21:02 ` Vlastimil Babka (SUSE)
2026-09-12 22:07 ` Hugh Dickins
2026-09-09 9:55 ` [PATCH v2 07/26] mm/fbatch: LRU_NEXT_ACTIVATE bit to optimize folio_activate() Hugh Dickins
2026-09-10 12:01 ` Vlastimil Babka (SUSE)
2026-09-12 22:35 ` Hugh Dickins
2026-09-10 16:42 ` Kiryl Shutsemau
2026-09-09 9:57 ` [PATCH v2 08/26] mm/fbatch: replace mlock_new_folio() by __folio_add_lru(,mlockit) Hugh Dickins
2026-09-10 17:47 ` Vlastimil Babka (SUSE)
2026-09-09 9:59 ` [PATCH v2 09/26] mm/fbatch: restore mlock+munlock batching, without extra ref Hugh Dickins
2026-09-10 21:05 ` Vlastimil Babka (SUSE)
2026-09-12 23:46 ` Hugh Dickins
2026-09-09 10:01 ` [PATCH v2 10/26] mm/fbatch: remove several uses of mlock_drain_local() Hugh Dickins
2026-09-09 10:03 ` [PATCH v2 11/26] mm/fbatch: remove migration's PAGE_WAS_MLOCKED lru_add_drain() Hugh Dickins
2026-09-09 10:05 ` [PATCH v2 12/26] mm/fbatch: remove percpu_pvec_drained and folios_put() Hugh Dickins
2026-09-09 10:08 ` [PATCH v2 13/26] mm/fbatch: no lru_add_drain to collect_longterm_unpinnable_folios() Hugh Dickins
2026-09-09 10:10 ` [PATCH v2 14/26] mm/fbatch: no lru_add_drain() nor _all() for memfd_wait_for_pins() Hugh Dickins
2026-09-09 10:12 ` [PATCH v2 15/26] mm/fbatch: remove shake_folio() shake_page() from memory-failure Hugh Dickins
2026-09-09 10:14 ` [PATCH v2 16/26] mm/fbatch: remove lru_cache_disable() from NUMA folio migration Hugh Dickins
2026-09-09 10:16 ` [PATCH v2 17/26] mm/fbatch: no lru_cache_disable() in __alloc_contig_migrate_range() Hugh Dickins
2026-09-09 10:18 ` [PATCH v2 18/26] mm/fbatch: remove lru_add_drain() and _all() calls from various Hugh Dickins
2026-09-09 10:20 ` [PATCH v2 19/26] mm/fbatch: vm/stat_refresh include lru_add_drain() on each cpu Hugh Dickins
2026-09-09 10:23 ` [PATCH v2 20/26] s390/fbatch: no lru_add_drain_all() in s390_wiggle_split_folio() Hugh Dickins
2026-09-09 10:25 ` [PATCH v2 21/26] block/fbatch: no lru_add_drain_all() in invalidate_bdev() Hugh Dickins
2026-09-09 10:27 ` [PATCH v2 22/26] fs/fbatch: drop_caches invalidate_bh_lrus() not lru_add_drain_all() Hugh Dickins
2026-09-09 10:30 ` [PATCH v2 23/26] fs,mm/fbatch: use invalidate_bh_lrus() not invalidate_bh_lrus_cpu() Hugh Dickins
2026-09-09 10:33 ` [PATCH v2 24/26] fs,mm/fbatch: lru_cache_disable() keep off buffer_head lrus only Hugh Dickins
2026-09-09 10:35 ` [PATCH v2 25/26] mm/fbatch: move lru_add_drain_all() declaration to mm/internal.h Hugh Dickins
2026-09-09 10:37 ` [PATCH v2 26/26] mm/fbatch: drop reference inside the loop when draining Hugh Dickins
2026-09-09 10:41 ` [PATCH v2 27/26] mm/fbatch: paranoid folio vmstats in folio_batch_move_lru() Hugh Dickins
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=3f712e41-78a8-5622-67dc-f462d2c97d11@google.com \
--to=hughd@google.com \
--cc=ackerleytng@google.com \
--cc=akpm@linux-foundation.org \
--cc=alex@ghiti.fr \
--cc=axboe@kernel.dk \
--cc=baohua@kernel.org \
--cc=baolin.wang@linux.alibaba.com \
--cc=bigeasy@linutronix.de \
--cc=binbin.wu@linux.intel.com \
--cc=brauner@kernel.org \
--cc=cl@gentwo.org \
--cc=david@kernel.org \
--cc=hannes@cmpxchg.org \
--cc=hch@lst.de \
--cc=imbrenda@linux.ibm.com \
--cc=jack@suse.cz \
--cc=jp.kobryn@linux.dev \
--cc=kas@kernel.org \
--cc=lance.yang@linux.dev \
--cc=leobras.c@gmail.com \
--cc=linmiaohe@huawei.com \
--cc=linux-block@vger.kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=mgorman@techsingularity.net \
--cc=mhocko@suse.com \
--cc=minchan@kernel.org \
--cc=mtosatti@redhat.com \
--cc=muchun.song@linux.dev \
--cc=osalvador@suse.de \
--cc=peterz@infradead.org \
--cc=qi.zheng@linux.dev \
--cc=riel@surriel.com \
--cc=ryncsn@gmail.com \
--cc=shakeel.butt@linux.dev \
--cc=surenb@google.com \
--cc=vbabka@kernel.org \
--cc=viro@zeniv.linux.org.uk \
--cc=willy@infradead.org \
--cc=yang@os.amperecomputing.com \
--cc=yuzhao@google.com \
--cc=ziy@nvidia.com \
--cc=zokeefe@google.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox