* [PATCH 01/25] mm/fbatch: remove !CONFIG_SMP special case of folio_activate()
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
@ 2026-08-24 13:52 ` Hugh Dickins
2026-08-24 13:55 ` [PATCH 02/25] mm/fbatch: allow folios_put_refs() to skip xa_is_value() entries Hugh Dickins
` (23 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 13:52 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
3.0 commit eb709b0d062e ("mm: batch activate_page() to reduce lock
contention") brought in an ifdef CONFIG_SMP around activate batching:
https://lore.kernel.org/linux-mm/20100805140755.501af8a7.akpm@linux-foundation.org/
shows a sensitivity to bloat that day, not any incompatibility with UP.
No other batching here has a UP alternative, and it's a bit confusing:
simplify mm/folio.c a little by removing it now.
Certainly we can reduce UP bloat (and/or 32-bit bloat) by, say, lowering
FOLIO_BATCH_SIZE from 31: traditionally 16, 14, 15, then raised to 31 by
6.9 commit 9cecde80aae0 ("mm: increase folio batch size"); or by giving
just the static per-cpu folio batches a type of their own with a smaller
array size on UP (1? or a little batching worthwhile even on UP?). But
not right now, it's orthogonal to this series.
And I suspect that the old ifdef led to lru_activate being placed last,
whereas it's usually the second most popular fbatch: move it there, to
match cpu_needs_drain() comment "Check these in order of likelihood that
they're not zero".
Signed-off-by: Hugh Dickins <hughd@google.com>
---
mm/folio.c | 40 ++++++----------------------------------
1 file changed, 6 insertions(+), 34 deletions(-)
diff --git a/mm/folio.c b/mm/folio.c
index d2937600cf72..62b96c9ce19e 100644
--- a/mm/folio.c
+++ b/mm/folio.c
@@ -47,12 +47,10 @@ struct cpu_fbatches {
*/
local_lock_t lock;
struct folio_batch lru_add;
+ struct folio_batch lru_activate;
struct folio_batch lru_deactivate_file;
struct folio_batch lru_deactivate;
struct folio_batch lru_lazyfree;
-#ifdef CONFIG_SMP
- struct folio_batch lru_activate;
-#endif
/* Protecting the following batches which require disabling interrupts */
local_lock_t lock_irq;
struct folio_batch lru_move_tail;
@@ -349,15 +347,6 @@ static void lru_activate(struct lruvec *lruvec, struct folio *folio)
count_memcg_events(lruvec_memcg(lruvec), PGACTIVATE, nr_pages);
}
-#ifdef CONFIG_SMP
-static void folio_activate_drain(int cpu)
-{
- struct folio_batch *fbatch = &per_cpu(cpu_fbatches.lru_activate, cpu);
-
- if (folio_batch_count(fbatch))
- folio_batch_move_lru(fbatch, lru_activate);
-}
-
void folio_activate(struct folio *folio)
{
if (folio_test_active(folio) || folio_test_unevictable(folio) ||
@@ -367,25 +356,6 @@ void folio_activate(struct folio *folio)
folio_batch_add_and_move(folio, lru_activate);
}
-#else
-static inline void folio_activate_drain(int cpu)
-{
-}
-
-void folio_activate(struct folio *folio)
-{
- struct lruvec *lruvec;
-
- if (!folio_test_clear_lru(folio))
- return;
-
- lruvec = folio_lruvec_lock_irq(folio);
- lru_activate(lruvec, folio);
- lruvec_unlock_irq(lruvec);
- folio_set_lru(folio);
-}
-#endif
-
static void __lru_cache_activate_folio(struct folio *folio)
{
struct folio_batch *fbatch;
@@ -694,6 +664,10 @@ void lru_add_drain_cpu(int cpu)
trace_mm_lru_add_drain_tp(cpu, nr_folios);
}
+ fbatch = &fbatches->lru_activate;
+ if (folio_batch_count(fbatch))
+ folio_batch_move_lru(fbatch, lru_activate);
+
fbatch = &fbatches->lru_move_tail;
/* Disabling interrupts below acts as a compiler barrier. */
if (data_race(folio_batch_count(fbatch))) {
@@ -716,8 +690,6 @@ void lru_add_drain_cpu(int cpu)
fbatch = &fbatches->lru_lazyfree;
if (folio_batch_count(fbatch))
folio_batch_move_lru(fbatch, lru_lazyfree);
-
- folio_activate_drain(cpu);
}
/**
@@ -825,11 +797,11 @@ static bool cpu_needs_drain(unsigned int cpu)
/* Check these in order of likelihood that they're not zero */
return data_race(folio_batch_count(&fbatches->lru_add) ||
+ folio_batch_count(&fbatches->lru_activate) ||
folio_batch_count(&fbatches->lru_move_tail) ||
folio_batch_count(&fbatches->lru_deactivate_file) ||
folio_batch_count(&fbatches->lru_deactivate) ||
folio_batch_count(&fbatches->lru_lazyfree) ||
- folio_batch_count(&fbatches->lru_activate) ||
need_mlock_drain(cpu)) ||
has_bh_in_lru(cpu, NULL);
}
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH 02/25] mm/fbatch: allow folios_put_refs() to skip xa_is_value() entries
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
2026-08-24 13:52 ` [PATCH 01/25] mm/fbatch: remove !CONFIG_SMP special case of folio_activate() Hugh Dickins
@ 2026-08-24 13:55 ` Hugh Dickins
2026-08-24 13:58 ` [PATCH 03/25] mm/fbatch: temporarily disable lazyfree and mlock+munlock batching Hugh Dickins
` (22 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 13:55 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Andrew Morton, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
Let folios_put_refs() (hence folio_batch_release()) skip xa_is_value()
entries, and therefore remove unneeded folio_batch_remove_exceptionals().
It made some sense when introduced in 3.1 for shmem swap entries only,
but workingset shadows popularized exceptional entries in 3.15, and it's
silly for so many sites to be squashing exceptionals out of the fbatch,
merely to suit an inadequacy in folios_put_refs().
But remove exceptionals on leaving truncate_folio_batch_exceptionals(),
one of whose callers then passes the fbatch on to others less tolerant.
No longer essential to this series, since 7.2 commit 9669b87065a6
("mm/lruvec: preemptively free dead folios during lru_add drain")
allowed folios_put_refs() to skip NULLs; but still an improvement.
Signed-off-by: Hugh Dickins <hughd@google.com>
---
include/linux/folio_batch.h | 5 +----
mm/folio.c | 25 ++++---------------------
mm/shmem.c | 2 --
mm/truncate.c | 16 ++++++++++------
4 files changed, 15 insertions(+), 33 deletions(-)
diff --git a/include/linux/folio_batch.h b/include/linux/folio_batch.h
index b45946adc50b..e1cc8ae023f1 100644
--- a/include/linux/folio_batch.h
+++ b/include/linux/folio_batch.h
@@ -22,8 +22,7 @@ struct folio;
* The folio_batch is used to amortise the cost of retrieving and
* operating on a set of folios. The order of folios in the batch may be
* significant (eg delete_from_page_cache_batch()). Some users of the
- * folio_batch store "exceptional" entries in it which can be removed
- * by calling folio_batch_remove_exceptionals().
+ * folio_batch store "exceptional" (xa_is_value) entries in it too.
*/
struct folio_batch {
unsigned char nr;
@@ -100,6 +99,4 @@ static inline void folio_batch_release(struct folio_batch *fbatch)
if (folio_batch_count(fbatch))
__folio_batch_release(fbatch);
}
-
-void folio_batch_remove_exceptionals(struct folio_batch *fbatch);
#endif /* _LINUX_FOLIO_BATCH_H */
diff --git a/mm/folio.c b/mm/folio.c
index 62b96c9ce19e..b2bce6b77498 100644
--- a/mm/folio.c
+++ b/mm/folio.c
@@ -983,6 +983,10 @@ void folios_put_refs(struct folio_batch *folios, unsigned int *refs)
if (!folio)
continue;
+ /* Skip any "exceptional" (workingset or shmem swap) entry. */
+ if (xa_is_value(folio))
+ continue;
+
if (is_huge_zero_folio(folio))
continue;
@@ -1088,27 +1092,6 @@ void __folio_batch_release(struct folio_batch *fbatch)
}
EXPORT_SYMBOL(__folio_batch_release);
-/**
- * folio_batch_remove_exceptionals() - Prune non-folios from a batch.
- * @fbatch: The batch to prune
- *
- * find_get_entries() fills a batch with both folios and shadow/swap/DAX
- * entries. This function prunes all the non-folio entries from @fbatch
- * without leaving holes, so that it can be passed on to folio-only batch
- * operations.
- */
-void folio_batch_remove_exceptionals(struct folio_batch *fbatch)
-{
- unsigned int i, j;
-
- for (i = 0, j = 0; i < folio_batch_count(fbatch); i++) {
- struct folio *folio = fbatch->folios[i];
- if (!xa_is_value(folio))
- fbatch->folios[j++] = folio;
- }
- fbatch->nr = j;
-}
-
#ifdef CONFIG_MEMCG
static void lruvec_reparent_lru(struct lruvec *child_lruvec,
struct lruvec *parent_lruvec,
diff --git a/mm/shmem.c b/mm/shmem.c
index 89a1495e55f7..3911721e1e55 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -1156,7 +1156,6 @@ static void shmem_undo_range(struct inode *inode, loff_t lstart, uoff_t lend,
truncate_inode_folio(mapping, folio);
folio_unlock(folio);
}
- folio_batch_remove_exceptionals(&fbatch);
folio_batch_release(&fbatch);
cond_resched();
}
@@ -1276,7 +1275,6 @@ static void shmem_undo_range(struct inode *inode, loff_t lstart, uoff_t lend,
}
folio_unlock(folio);
}
- folio_batch_remove_exceptionals(&fbatch);
folio_batch_release(&fbatch);
}
diff --git a/mm/truncate.c b/mm/truncate.c
index b58ba940be47..4151f7a167e3 100644
--- a/mm/truncate.c
+++ b/mm/truncate.c
@@ -53,7 +53,7 @@ static void clear_shadow_entries(struct address_space *mapping,
/*
* Unconditionally remove exceptional entries. Usually called from truncate
* path. Note that the folio_batch may be altered by this function by removing
- * exceptional entries similar to what folio_batch_remove_exceptionals() does.
+ * exceptional entries.
* Please note that indices[] has entries in ascending order as guaranteed by
* either find_get_entries() or find_lock_entries().
*/
@@ -95,7 +95,7 @@ static void truncate_folio_batch_exceptionals(struct address_space *mapping,
dax_delete_mapping_entry(mapping, indices[i]);
}
}
- goto out;
+ goto squash;
}
xas_set(&xas, indices[j]);
@@ -113,8 +113,14 @@ static void truncate_folio_batch_exceptionals(struct address_space *mapping,
if (mapping_shrinkable(mapping))
inode_lru_list_add(mapping->host);
spin_unlock(&mapping->host->i_lock);
-out:
- folio_batch_remove_exceptionals(fbatch);
+
+squash:
+ for (i = j + 1; i < nr; i++) {
+ folio = fbatch->folios[i];
+ if (!xa_is_value(folio))
+ fbatch->folios[j++] = folio;
+ }
+ fbatch->nr = j;
}
/**
@@ -575,7 +581,6 @@ unsigned long mapping_try_invalidate(struct address_space *mapping,
if (xa_has_values)
clear_shadow_entries(mapping, indices[0], indices[nr-1]);
- folio_batch_remove_exceptionals(&fbatch);
folio_batch_release(&fbatch);
cond_resched();
}
@@ -732,7 +737,6 @@ int invalidate_inode_pages2_range(struct address_space *mapping,
if (xa_has_values)
clear_shadow_entries(mapping, indices[0], indices[nr-1]);
- folio_batch_remove_exceptionals(&fbatch);
folio_batch_release(&fbatch);
cond_resched();
}
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH 03/25] mm/fbatch: temporarily disable lazyfree and mlock+munlock batching
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
2026-08-24 13:52 ` [PATCH 01/25] mm/fbatch: remove !CONFIG_SMP special case of folio_activate() Hugh Dickins
2026-08-24 13:55 ` [PATCH 02/25] mm/fbatch: allow folios_put_refs() to skip xa_is_value() entries Hugh Dickins
@ 2026-08-24 13:58 ` Hugh Dickins
2026-08-24 14:01 ` [PATCH 04/25] mm/fbatch: lru bit set, no extra ref, while folio on per-cpu fbatch Hugh Dickins
` (21 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 13:58 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
It will not matter if we occasionally get activation or deactivation
wrong; but a mistaken lazyfree (MADV_FREE) would likely cause dataloss.
So disable its batching while reworking the per-cpu fbatch handling,
then re-enable it with more thought afterwards.
Similarly disable mlock+munlock batching temporarily: they will need
some redesign before re-enabling. Just insert one disabling line for
now, leaving the rest of the code as it was, for consideration later.
Signed-off-by: Hugh Dickins <hughd@google.com>
---
mm/folio.c | 2 ++
mm/mlock.c | 3 +++
2 files changed, 5 insertions(+)
diff --git a/mm/folio.c b/mm/folio.c
index b2bce6b77498..a7010ae3edff 100644
--- a/mm/folio.c
+++ b/mm/folio.c
@@ -218,6 +218,8 @@ static void __folio_batch_add_and_move(struct folio_batch __percpu *fbatch,
local_lock(&cpu_fbatches.lock);
if (!folio_batch_add(this_cpu_ptr(fbatch), folio) ||
+ /* XXX Temporarily disable lazyfree batching */
+ fbatch == &cpu_fbatches.lru_lazyfree ||
!folio_may_be_lru_cached(folio) || lru_cache_disabled())
folio_batch_move_lru(this_cpu_ptr(fbatch), move_fn);
diff --git a/mm/mlock.c b/mm/mlock.c
index efa6716e4dfb..14c02e155d68 100644
--- a/mm/mlock.c
+++ b/mm/mlock.c
@@ -255,6 +255,7 @@ void mlock_folio(struct folio *folio)
folio_get(folio);
if (!folio_batch_add(fbatch, mlock_lru(folio)) ||
+ true || /* XXX Temporarily disable mlock batching */
!folio_may_be_lru_cached(folio) || lru_cache_disabled())
mlock_folio_batch(fbatch);
local_unlock(&mlock_fbatch.lock);
@@ -278,6 +279,7 @@ void mlock_new_folio(struct folio *folio)
folio_get(folio);
if (!folio_batch_add(fbatch, mlock_new(folio)) ||
+ true || /* XXX Temporarily disable mlock_new batching */
!folio_may_be_lru_cached(folio) || lru_cache_disabled())
mlock_folio_batch(fbatch);
local_unlock(&mlock_fbatch.lock);
@@ -299,6 +301,7 @@ void munlock_folio(struct folio *folio)
*/
folio_get(folio);
if (!folio_batch_add(fbatch, folio) ||
+ true || /* XXX Temporarily disable munlock batching */
!folio_may_be_lru_cached(folio) || lru_cache_disabled())
mlock_folio_batch(fbatch);
local_unlock(&mlock_fbatch.lock);
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH 04/25] mm/fbatch: lru bit set, no extra ref, while folio on per-cpu fbatch
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
` (2 preceding siblings ...)
2026-08-24 13:58 ` [PATCH 03/25] mm/fbatch: temporarily disable lazyfree and mlock+munlock batching Hugh Dickins
@ 2026-08-24 14:01 ` Hugh Dickins
2026-08-24 14:03 ` [PATCH 05/25] mm/fbatch: lru_add_del_folio()+folio_add_lru() after clear_lru() Hugh Dickins
` (20 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 14:01 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
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")
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.
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>
---
include/linux/mm_inline.h | 19 ++++++
include/linux/mm_types.h | 4 +-
mm/folio.c | 119 +++++++++++++-------------------------
mm/huge_memory.c | 6 +-
4 files changed, 66 insertions(+), 82 deletions(-)
diff --git a/include/linux/mm_inline.h b/include/linux/mm_inline.h
index 621c8653d8f7..1ecaf2ef9f2b 100644
--- a/include/linux/mm_inline.h
+++ b/include/linux/mm_inline.h
@@ -343,6 +343,23 @@ 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)
+{
+ /* BUG_ON(folio_test_lru(folio)); */
+ if (!(folio->lru_next & BIT(LRU_NEXT_BATCHED)))
+ return false;
+ folio->lru.next = LIST_POISON1;
+ /* BUG_ON(folio->lru_next & BIT(LRU_NEXT_BATCHED)); */
+ return true;
+}
+
static __always_inline
void lruvec_add_folio(struct lruvec *lruvec, struct folio *folio)
{
@@ -384,6 +401,8 @@ void lruvec_del_folio(struct lruvec *lruvec, struct folio *folio)
if (lru_gen_del_folio(lruvec, folio, false))
return;
+ if (lru_add_del_folio(folio))
+ return;
if (lru != LRU_UNEVICTABLE)
list_del(&folio->lru);
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index 939b5ea8c9e0..2b1a1f983a91 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -410,10 +410,8 @@ struct folio {
union {
struct list_head lru;
/* private: avoid cluttering the output */
- /* For the Unevictable "LRU list" slot */
struct {
- /* Avoid compound_info */
- void *__filler;
+ unsigned long lru_next;
/* public: */
unsigned int mlock_count;
/* private: */
diff --git a/mm/folio.c b/mm/folio.c
index a7010ae3edff..88e3ebd7e652 100644
--- a/mm/folio.c
+++ b/mm/folio.c
@@ -151,57 +151,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);
+restore_lru:
folio_set_lru(folio);
}
if (lruvec)
lruvec_unlock_irqrestore(lruvec, flags);
-
- /* Cleanup filtered dead folios. */
- if (is_lru_add) {
- mem_cgroup_uncharge_folios(&free_fbatch);
- free_unref_folios(&free_fbatch);
- }
-
folios_put(fbatch);
}
@@ -210,8 +187,6 @@ static void __folio_batch_add_and_move(struct folio_batch __percpu *fbatch,
{
unsigned long flags;
- folio_get(folio);
-
if (disable_irq)
local_lock_irqsave(&cpu_fbatches.lock_irq, flags);
else
@@ -339,7 +314,6 @@ static void lru_activate(struct lruvec *lruvec, struct folio *folio)
if (folio_test_active(folio) || folio_test_unevictable(folio))
return;
-
lruvec_del_folio(lruvec, folio);
folio_set_active(folio);
lruvec_add_folio(lruvec, folio);
@@ -355,37 +329,12 @@ void folio_activate(struct folio *folio)
!folio_test_lru(folio))
return;
- folio_batch_add_and_move(folio, lru_activate);
-}
-
-static void __lru_cache_activate_folio(struct folio *folio)
-{
- struct folio_batch *fbatch;
- int i;
-
- local_lock(&cpu_fbatches.lock);
- fbatch = this_cpu_ptr(&cpu_fbatches.lru_add);
-
/*
- * Search backwards on the optimistic assumption that the folio being
- * activated has just been added to this batch. Note that only
- * the local batch is examined as a !LRU folio could be in the
- * process of being released, reclaimed, migrated or on a remote
- * batch that is currently being drained. Furthermore, marking
- * a remote batch's folio active potentially hits a race where
- * a folio is marked active just after it is added to the inactive
- * list causing accounting errors and BUG_ON checks to trigger.
+ * XXX: It is curiously difficult to recreate safely the old
+ * __lru_cache_activate_folio() optimization (folio_set_active()
+ * directly if it's on the local lru_add fbatch): revisit later.
*/
- for (i = folio_batch_count(fbatch) - 1; i >= 0; i--) {
- struct folio *batch_folio = fbatch->folios[i];
-
- if (batch_folio == folio) {
- folio_set_active(folio);
- break;
- }
- }
-
- local_unlock(&cpu_fbatches.lock);
+ folio_batch_add_and_move(folio, lru_activate);
}
#ifdef CONFIG_LRU_GEN
@@ -476,16 +425,7 @@ void folio_mark_accessed(struct folio *folio)
* unevictable page accessed has no effect.
*/
} else if (!folio_test_active(folio)) {
- /*
- * If the folio is on the LRU, queue it for activation via
- * cpu_fbatches.lru_activate. Otherwise, assume the folio is in a
- * folio_batch, mark it active and it'll be moved to the active
- * LRU on the next drain.
- */
- if (folio_test_lru(folio))
- folio_activate(folio);
- else
- __lru_cache_activate_folio(folio);
+ folio_activate(folio);
folio_clear_referenced(folio);
workingset_activation(folio);
}
@@ -505,6 +445,10 @@ EXPORT_SYMBOL(folio_mark_accessed);
*/
void folio_add_lru(struct folio *folio)
{
+ struct folio_batch *fbatch;
+ unsigned long lru_next;
+ bool full;
+
VM_BUG_ON_FOLIO(folio_test_active(folio) &&
folio_test_unevictable(folio), folio);
VM_BUG_ON_FOLIO(folio_test_lru(folio), folio);
@@ -524,7 +468,26 @@ void folio_add_lru(struct folio *folio)
folio_mark_accessed(folio);
}
- folio_batch_add_and_move(folio, lru_add);
+ local_lock(&cpu_fbatches.lock);
+ fbatch = this_cpu_ptr(&cpu_fbatches.lru_add);
+
+ /* Storing this address is only for debugging */
+ lru_next = (unsigned long)&fbatch->folios[fbatch->nr];
+ /* This mask will do nothing on 64-bit */
+ lru_next &= ~(BIT(NR_LRU_NEXT_FLAGS) - 1);
+ lru_next |= BIT(LRU_NEXT_BATCHED);
+ folio->lru_next = lru_next;
+
+ full = !folio_batch_add(fbatch, folio);
+
+ /* Ensure folio->lru_next visible to folio_test_clear_lru() callers */
+ smp_mb__before_atomic();
+ folio_set_lru(folio);
+
+ if (full || !folio_may_be_lru_cached(folio) || lru_cache_disabled())
+ folio_batch_move_lru(fbatch, lru_add);
+
+ local_unlock(&cpu_fbatches.lock);
}
EXPORT_SYMBOL(folio_add_lru);
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 644d6905b49c..98b1d0ea50f0 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3993,8 +3993,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);
+ }
ret = __split_unmapped_folio(folio, new_order, split_at, xas,
mapping, split_type);
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH 05/25] mm/fbatch: lru_add_del_folio()+folio_add_lru() after clear_lru()
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
` (3 preceding siblings ...)
2026-08-24 14:01 ` [PATCH 04/25] mm/fbatch: lru bit set, no extra ref, while folio on per-cpu fbatch Hugh Dickins
@ 2026-08-24 14:03 ` Hugh Dickins
2026-08-24 14:06 ` [PATCH 06/25] mm/fbatch: fbatch_drain_lazyfree(onstack fbatch) before ptl unlock Hugh Dickins
` (19 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 14:03 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
Most callers of folio_test_clear_lru() then proceed to remove the folio
from its lru, and add it back at the end when they're done (if still in
use). But isolate_migratepages_block() and check_move_unevictable_pages()
sometimes decide against, and release immediately with a folio_set_lru().
Which usually works fine: but there's now a small chance that while they
held the folio with lru bit cleared, an lru_add fbatch drain came along,
and had to skip that folio because its lru bit was transiently cleared
(previously, the lru_add fbatch drain relied on finding lru bit never yet
set). This risks leaving that folio off lru, unreclaimable until freed.
Fix such cases by trying lru_add_del_folio() (which only takes action and
returns true if the folio was on an lru_add fbatch), then folio_add_lru()
if it succeeded: invalidating the old fbatch slot, appending in a new one.
Signed-off-by: Hugh Dickins <hughd@google.com>
---
mm/compaction.c | 13 +++++++++++--
mm/vmscan.c | 16 +++++++++-------
2 files changed, 20 insertions(+), 9 deletions(-)
diff --git a/mm/compaction.c b/mm/compaction.c
index a049415512c6..9e045a90ba21 100644
--- a/mm/compaction.c
+++ b/mm/compaction.c
@@ -1204,7 +1204,13 @@ isolate_migratepages_block(struct compact_control *cc, unsigned long low_pfn,
!cc->alloc_contig)) {
low_pfn += folio_nr_pages(folio) - 1;
nr_scanned += folio_nr_pages(folio) - 1;
- folio_set_lru(folio);
+ if (lru_add_del_folio(folio)) {
+ lruvec_unlock_irqrestore(locked, flags);
+ folio_add_lru(folio);
+ locked = NULL;
+ } else {
+ folio_set_lru(folio);
+ }
goto isolate_fail_put;
}
}
@@ -1293,7 +1299,10 @@ isolate_migratepages_block(struct compact_control *cc, unsigned long low_pfn,
if (locked)
lruvec_unlock_irqrestore(locked, flags);
if (folio) {
- folio_set_lru(folio);
+ if (lru_add_del_folio(folio))
+ folio_add_lru(folio);
+ else
+ folio_set_lru(folio);
folio_put(folio);
}
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 4ca9775ceee8..7da12ffbd3bd 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -8021,17 +8021,19 @@ void check_move_unevictable_folios(struct folio_batch *fbatch)
folio_clear_unevictable(folio);
lruvec_add_folio(lruvec, folio);
pgrescued += nr_pages;
+ } else if (lru_add_del_folio(folio)) {
+ lruvec_unlock_irq(lruvec);
+ folio_add_lru(folio);
+ lruvec = NULL;
}
- folio_set_lru(folio);
+ if (lruvec)
+ folio_set_lru(folio);
}
- if (lruvec) {
- __count_vm_events(UNEVICTABLE_PGRESCUED, pgrescued);
- __count_vm_events(UNEVICTABLE_PGSCANNED, pgscanned);
+ if (lruvec)
lruvec_unlock_irq(lruvec);
- } else if (pgscanned) {
- count_vm_events(UNEVICTABLE_PGSCANNED, pgscanned);
- }
+ count_vm_events(UNEVICTABLE_PGRESCUED, pgrescued);
+ count_vm_events(UNEVICTABLE_PGSCANNED, pgscanned);
}
EXPORT_SYMBOL_GPL(check_move_unevictable_folios);
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH 06/25] mm/fbatch: fbatch_drain_lazyfree(onstack fbatch) before ptl unlock
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
` (4 preceding siblings ...)
2026-08-24 14:03 ` [PATCH 05/25] mm/fbatch: lru_add_del_folio()+folio_add_lru() after clear_lru() Hugh Dickins
@ 2026-08-24 14:06 ` Hugh Dickins
2026-08-24 14:09 ` [PATCH 07/25] mm/fbatch: LRU_NEXT_ACTIVATE bit to optimize folio_activate() Hugh Dickins
` (18 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 14:06 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
Re-enable lazyfree batching for MADV_FREE. But it's not safe now to leave
potentially stale (then reused) folios in a per-cpu fbatch for lazyfree.
Instead, madvise_free_pte_range() keep an fbatch on its stack, and drain
it each time before dropping pagetable lock, while the folios are secure.
Ignore folio_may_be_lru_cached() and lru_cache_disabled(): limitations
irrelevant to this fbatch drained under spinlock (even if RT); though
in practice madvise_free_huge_pmd() does have to drain every time.
Signed-off-by: Hugh Dickins <hughd@google.com>
---
include/linux/huge_mm.h | 6 ++++--
mm/folio.c | 34 +++++++++++++++++++++-------------
mm/huge_memory.c | 6 ++++--
mm/internal.h | 3 ++-
mm/madvise.c | 9 +++++++--
5 files changed, 38 insertions(+), 20 deletions(-)
diff --git a/include/linux/huge_mm.h b/include/linux/huge_mm.h
index c745f7ad2298..d50906327d1d 100644
--- a/include/linux/huge_mm.h
+++ b/include/linux/huge_mm.h
@@ -24,9 +24,11 @@ static inline void huge_pud_set_accessed(struct vm_fault *vmf, pud_t orig_pud)
}
#endif
-vm_fault_t do_huge_pmd_wp_page(struct vm_fault *vmf);
+struct folio_batch;
bool madvise_free_huge_pmd(struct mmu_gather *tlb, struct vm_area_struct *vma,
- pmd_t *pmd, unsigned long addr, unsigned long next);
+ pmd_t *pmd, unsigned long addr, unsigned long next,
+ struct folio_batch *fbatch);
+vm_fault_t do_huge_pmd_wp_page(struct vm_fault *vmf);
bool zap_huge_pmd(struct mmu_gather *tlb, struct vm_area_struct *vma, pmd_t *pmd,
unsigned long addr);
int zap_huge_pud(struct mmu_gather *tlb, struct vm_area_struct *vma, pud_t *pud,
diff --git a/mm/folio.c b/mm/folio.c
index 88e3ebd7e652..e76868c95acc 100644
--- a/mm/folio.c
+++ b/mm/folio.c
@@ -50,7 +50,6 @@ struct cpu_fbatches {
struct folio_batch lru_activate;
struct folio_batch lru_deactivate_file;
struct folio_batch lru_deactivate;
- struct folio_batch lru_lazyfree;
/* Protecting the following batches which require disabling interrupts */
local_lock_t lock_irq;
struct folio_batch lru_move_tail;
@@ -193,8 +192,6 @@ static void __folio_batch_add_and_move(struct folio_batch __percpu *fbatch,
local_lock(&cpu_fbatches.lock);
if (!folio_batch_add(this_cpu_ptr(fbatch), folio) ||
- /* XXX Temporarily disable lazyfree batching */
- fbatch == &cpu_fbatches.lru_lazyfree ||
!folio_may_be_lru_cached(folio) || lru_cache_disabled())
folio_batch_move_lru(this_cpu_ptr(fbatch), move_fn);
@@ -651,10 +648,6 @@ void lru_add_drain_cpu(int cpu)
fbatch = &fbatches->lru_deactivate;
if (folio_batch_count(fbatch))
folio_batch_move_lru(fbatch, lru_deactivate);
-
- fbatch = &fbatches->lru_lazyfree;
- if (folio_batch_count(fbatch))
- folio_batch_move_lru(fbatch, lru_lazyfree);
}
/**
@@ -700,19 +693,35 @@ void folio_deactivate(struct folio *folio)
/**
* folio_mark_lazyfree - make an anon folio lazyfree
- * @folio: folio to deactivate
+ * @fbatch: batch to which folio will be added
+ * @folio: folio to be lazily freed
*
- * folio_mark_lazyfree() moves @folio to the inactive file list.
- * This is done to accelerate the reclaim of @folio.
+ * folio_mark_lazyfree() moves @folio to the inactive file list
+ * via @fbatch. This is done to accelerate the reclaim of @folio.
*/
-void folio_mark_lazyfree(struct folio *folio)
+void folio_mark_lazyfree(struct folio_batch *fbatch, struct folio *folio)
{
if (!folio_test_anon(folio) || !folio_test_swapbacked(folio) ||
!folio_test_lru(folio) ||
folio_test_swapcache(folio) || folio_test_unevictable(folio))
return;
- folio_batch_add_and_move(folio, lru_lazyfree);
+ if (!folio_batch_add(fbatch, folio))
+ folio_batch_move_lru(fbatch, lru_lazyfree);
+}
+
+/**
+ * fbatch_drain_lazyfree - drain the caller's folio batch
+ * @fbatch: batch of folios to be lazily freed
+ *
+ * Must be called before caller drops the page table lock: that is,
+ * before dropping the last certain reference to the folios in @fbatch.
+ * It would be very bad to lazyfree a folio after it was freed and reused.
+ */
+void fbatch_drain_lazyfree(struct folio_batch *fbatch)
+{
+ if (folio_batch_count(fbatch))
+ folio_batch_move_lru(fbatch, lru_lazyfree);
}
void lru_add_drain(void)
@@ -766,7 +775,6 @@ static bool cpu_needs_drain(unsigned int cpu)
folio_batch_count(&fbatches->lru_move_tail) ||
folio_batch_count(&fbatches->lru_deactivate_file) ||
folio_batch_count(&fbatches->lru_deactivate) ||
- folio_batch_count(&fbatches->lru_lazyfree) ||
need_mlock_drain(cpu)) ||
has_bh_in_lru(cpu, NULL);
}
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 98b1d0ea50f0..b1f315400111 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -2356,7 +2356,8 @@ vm_fault_t do_huge_pmd_numa_page(struct vm_fault *vmf)
* Otherwise, return false.
*/
bool madvise_free_huge_pmd(struct mmu_gather *tlb, struct vm_area_struct *vma,
- pmd_t *pmd, unsigned long addr, unsigned long next)
+ pmd_t *pmd, unsigned long addr, unsigned long next,
+ struct folio_batch *fbatch)
{
spinlock_t *ptl;
pmd_t orig_pmd;
@@ -2417,7 +2418,8 @@ bool madvise_free_huge_pmd(struct mmu_gather *tlb, struct vm_area_struct *vma,
tlb_remove_pmd_tlb_entry(tlb, pmd, addr);
}
- folio_mark_lazyfree(folio);
+ folio_mark_lazyfree(fbatch, folio);
+ fbatch_drain_lazyfree(fbatch);
ret = true;
out:
spin_unlock(ptl);
diff --git a/mm/internal.h b/mm/internal.h
index 68db5abd0a4c..ababee1a8872 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -66,7 +66,8 @@ void lru_add_drain(void);
void lru_add_drain_cpu(int cpu);
void lru_add_drain_cpu_zone(struct zone *zone);
void folio_deactivate(struct folio *folio);
-void folio_mark_lazyfree(struct folio *folio);
+void folio_mark_lazyfree(struct folio_batch *fbatch, struct folio *folio);
+void fbatch_drain_lazyfree(struct folio_batch *fbatch);
/* mm/vmscan.c */
unsigned long zone_reclaimable_pages(struct zone *zone);
diff --git a/mm/madvise.c b/mm/madvise.c
index 240d9161ee74..6ef1f489123c 100644
--- a/mm/madvise.c
+++ b/mm/madvise.c
@@ -27,6 +27,7 @@
#include <linux/file.h>
#include <linux/blk_plug.h>
#include <linux/backing-dev.h>
+#include <linux/folio_batch.h>
#include <linux/pagewalk.h>
#include <linux/swap.h>
#include <linux/leafops.h>
@@ -657,6 +658,7 @@ static int madvise_free_pte_range(pmd_t *pmd, unsigned long addr,
struct mmu_gather *tlb = walk->private;
struct mm_struct *mm = tlb->mm;
struct vm_area_struct *vma = walk->vma;
+ struct folio_batch fbatch;
spinlock_t *ptl;
pte_t *start_pte, *pte, ptent;
struct folio *folio;
@@ -664,9 +666,10 @@ static int madvise_free_pte_range(pmd_t *pmd, unsigned long addr,
unsigned long next;
int nr, max_nr;
+ folio_batch_init(&fbatch);
next = pmd_addr_end(addr, end);
if (pmd_trans_huge(*pmd))
- if (madvise_free_huge_pmd(tlb, vma, pmd, addr, next))
+ if (madvise_free_huge_pmd(tlb, vma, pmd, addr, next, &fbatch))
return 0;
tlb_change_page_size(tlb, PAGE_SIZE);
@@ -724,6 +727,7 @@ static int madvise_free_pte_range(pmd_t *pmd, unsigned long addr,
continue;
folio_get(folio);
lazy_mmu_mode_disable();
+ fbatch_drain_lazyfree(&fbatch);
pte_unmap_unlock(start_pte, ptl);
start_pte = NULL;
err = split_folio(folio);
@@ -768,13 +772,14 @@ static int madvise_free_pte_range(pmd_t *pmd, unsigned long addr,
clear_young_dirty_ptes(vma, addr, pte, nr, cydp_flags);
tlb_remove_tlb_entries(tlb, pte, nr, addr);
}
- folio_mark_lazyfree(folio);
+ folio_mark_lazyfree(&fbatch, folio);
}
if (nr_swap)
add_mm_counter(mm, MM_SWAPENTS, nr_swap);
if (start_pte) {
lazy_mmu_mode_disable();
+ fbatch_drain_lazyfree(&fbatch);
pte_unmap_unlock(start_pte, ptl);
}
cond_resched();
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH 07/25] mm/fbatch: LRU_NEXT_ACTIVATE bit to optimize folio_activate()
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
` (5 preceding siblings ...)
2026-08-24 14:06 ` [PATCH 06/25] mm/fbatch: fbatch_drain_lazyfree(onstack fbatch) before ptl unlock Hugh Dickins
@ 2026-08-24 14:09 ` Hugh Dickins
2026-08-24 14:11 ` [PATCH 08/25] mm/fbatch: replace mlock_new_folio() by __folio_add_lru(,mlockit) Hugh Dickins
` (17 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 14:09 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
Implement an equivalent to the old __lru_cache_activate_folio()
optimization, to activate a folio recently put in the lru_add fbatch,
without having to put it through the lru_activate fbatch too. Neither
lruvec lock nor lru bit can guard this safely and efficiently, so resort
to try_cmpxchg() on a further, LRU_NEXT_ACTIVATE bit in folio->lru_next.
Signed-off-by: Hugh Dickins <hughd@google.com>
---
include/linux/mm_inline.h | 3 +++
mm/folio.c | 23 ++++++++++++++++++++---
2 files changed, 23 insertions(+), 3 deletions(-)
diff --git a/include/linux/mm_inline.h b/include/linux/mm_inline.h
index 1ecaf2ef9f2b..1b54900e87f0 100644
--- a/include/linux/mm_inline.h
+++ b/include/linux/mm_inline.h
@@ -346,6 +346,7 @@ static inline void folio_migrate_refs(struct folio *new, const struct folio *old
enum {
LRU_NEXT_NEVER_TAIL = 0, /* Used by a tail's compound_head */
LRU_NEXT_BATCHED = 1, /* Not used by any aligned pointer */
+ LRU_NEXT_ACTIVATE,
NR_LRU_NEXT_FLAGS
};
@@ -355,6 +356,8 @@ bool lru_add_del_folio(struct folio *folio)
/* BUG_ON(folio_test_lru(folio)); */
if (!(folio->lru_next & BIT(LRU_NEXT_BATCHED)))
return false;
+ if (folio->lru_next & BIT(LRU_NEXT_ACTIVATE))
+ folio_set_active(folio);
folio->lru.next = LIST_POISON1;
/* BUG_ON(folio->lru_next & BIT(LRU_NEXT_BATCHED)); */
return true;
diff --git a/mm/folio.c b/mm/folio.c
index e76868c95acc..0d8eb9cf5ad5 100644
--- a/mm/folio.c
+++ b/mm/folio.c
@@ -322,15 +322,32 @@ static void lru_activate(struct lruvec *lruvec, struct folio *folio)
void folio_activate(struct folio *folio)
{
+ unsigned long lru_next;
+
if (folio_test_active(folio) || folio_test_unevictable(folio) ||
!folio_test_lru(folio))
return;
/*
- * XXX: It is curiously difficult to recreate safely the old
- * __lru_cache_activate_folio() optimization (folio_set_active()
- * directly if it's on the local lru_add fbatch): revisit later.
+ * This optimization is intended for the common case of folio
+ * having been recently added to this CPU's lru_add fbatch.
+ * But since other CPUs can now take it at any instant (after
+ * a folio_test_clear_lru()), and we may be migrated to another
+ * CPU, it is simplest just to extend the optimization to all CPUs.
+ *
+ * folio_set_active() would be unsafe without the lruvec lock, and
+ * a folio_test_clear_lru() here might cause a racing drain of the
+ * lru_add fbatch to skip its lru_add(): so use try_cmpxchg().
*/
+ lru_next = READ_ONCE(folio->lru_next);
+ while (lru_next & BIT(LRU_NEXT_BATCHED)) {
+ if (lru_next & BIT(LRU_NEXT_ACTIVATE))
+ return;
+ if (try_cmpxchg(&folio->lru_next, &lru_next,
+ lru_next | BIT(LRU_NEXT_ACTIVATE)))
+ return;
+ }
+
folio_batch_add_and_move(folio, lru_activate);
}
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH 08/25] mm/fbatch: replace mlock_new_folio() by __folio_add_lru(,mlockit)
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
` (6 preceding siblings ...)
2026-08-24 14:09 ` [PATCH 07/25] mm/fbatch: LRU_NEXT_ACTIVATE bit to optimize folio_activate() Hugh Dickins
@ 2026-08-24 14:11 ` Hugh Dickins
2026-08-24 14:14 ` [PATCH 09/25] mm/fbatch: restore mlock+munlock batching, without extra ref Hugh Dickins
` (16 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 14:11 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
Replace mlock_new_folio(), working on mm/mlock.c's mlock_fbatch, by
__folio_add_lru(,mlockit), working on mm/folio.c's lru_add fbatch:
folio moved to lruvec by lru_add(), with its mlocking incidental.
Remove old comment about not needing smp_mb__after_atomic() from
lru_add(): but that is a detail which will need to be reconsidered.
Initialize mlock_count earlier, when adding to fbatch rather than when
adding to lruvec. mlock_count count in 2s, with the low bit set to
distinguish it from lru.prev. This helps when an mlocked folio is put
back early by compaction, but will enable further optimization next.
Change mlock_count from unsigned int to long: long to match pointer
without endian concerns, signed for better treatment of those rare
cases when final munlocks precede their still batched mlocks.
This is an intermediate, poorly tested review stage: mlock_new_folio()
code removed from mm/mlock.c, remaining code there updated to respect
the new mlock_count accounting, but not considered beyond that.
Signed-off-by: Hugh Dickins <hughd@google.com>
---
Documentation/mm/unevictable-lru.rst | 2 +-
include/linux/mm_types.h | 8 +++-
include/linux/swap.h | 6 ++-
mm/folio.c | 59 ++++++++++++------------
mm/huge_memory.c | 2 +-
mm/internal.h | 4 +-
mm/mlock.c | 67 ++++------------------------
7 files changed, 52 insertions(+), 96 deletions(-)
diff --git a/Documentation/mm/unevictable-lru.rst b/Documentation/mm/unevictable-lru.rst
index 8d11fe6a0854..45b453226336 100644
--- a/Documentation/mm/unevictable-lru.rst
+++ b/Documentation/mm/unevictable-lru.rst
@@ -314,7 +314,7 @@ For each PTE (or PMD) being faulted into a VMA, the page add rmap function
calls mlock_vma_folio(), which calls mlock_folio() when the VMA is VM_LOCKED
(unless it is a PTE mapping of a part of a transparent huge page). Or when
it is a newly allocated anonymous page, folio_add_lru_vma() calls
-mlock_new_folio() instead: similar to mlock_folio(), but can make better
+__folio_add_lru(mlockit) instead: similar to mlock_folio(), but can make better
judgments, since this page is held exclusively and known not to be on LRU yet.
mlock_folio() sets PG_mlocked immediately, then places the page on the CPU's
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index 2b1a1f983a91..049b114aee09 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -359,7 +359,7 @@ typedef unsigned short mm_id_t;
* struct folio - Represents a contiguous set of bytes.
* @flags: Identical to the page flags.
* @lru: Least Recently Used list; tracks how recently this folio was used.
- * @mlock_count: Number of times this folio has been pinned by mlock().
+ * @mlock_count: Number of times this folio has been pinned by mlock() *2 +1
* @mapping: The file this page belongs to, or refers to the anon_vma for
* anonymous memory.
* @index: Offset within the file, in units of pages. For anonymous memory,
@@ -413,7 +413,7 @@ struct folio {
struct {
unsigned long lru_next;
/* public: */
- unsigned int mlock_count;
+ long mlock_count;
/* private: */
};
/* public: */
@@ -507,6 +507,10 @@ struct folio {
};
};
+/* folio's mlock_count is doubled, low bit set to distinguish from lru.prev */
+#define MLOCK_COUNT_0 1 /* Bit not set in any aligned pointer */
+#define MLOCK_COUNT_1 2 /* Increment or decrement mlock_count */
+
#define FOLIO_MATCH(pg, fl) \
static_assert(offsetof(struct page, pg) == offsetof(struct folio, fl))
FOLIO_MATCH(flags, flags);
diff --git a/include/linux/swap.h b/include/linux/swap.h
index 696ed01709c2..f21e1dd6febc 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -294,7 +294,11 @@ extern unsigned long totalreserve_pages;
#define nr_free_pages() global_zone_page_state(NR_FREE_PAGES)
/* linux/mm/folio.c */
-void folio_add_lru(struct folio *folio);
+void __folio_add_lru(struct folio *folio, bool mlockit);
+static inline void folio_add_lru(struct folio *folio)
+{
+ __folio_add_lru(folio, false);
+}
void folio_mark_accessed(struct folio *folio);
void lru_add_drain_all(void);
diff --git a/mm/folio.c b/mm/folio.c
index 0d8eb9cf5ad5..fa4cf9d7d51b 100644
--- a/mm/folio.c
+++ b/mm/folio.c
@@ -112,31 +112,12 @@ static void lru_add(struct lruvec *lruvec, struct folio *folio)
VM_BUG_ON_FOLIO(folio_test_lru(folio), folio);
- /*
- * Is an smp_mb__after_atomic() still required here, before
- * folio_evictable() tests the mlocked flag, to rule out the possibility
- * of stranding an evictable folio on an unevictable LRU? I think
- * not, because __munlock_folio() only clears the mlocked flag
- * while the LRU lock is held.
- *
- * (That is not true of __page_cache_release(), and not necessarily
- * true of folios_put(): but those only clear the mlocked flag after
- * folio_put_testzero() has excluded any other users of the folio.)
- */
if (folio_evictable(folio)) {
if (was_unevictable)
__count_vm_events(UNEVICTABLE_PGRESCUED, nr_pages);
} else {
folio_clear_active(folio);
folio_set_unevictable(folio);
- /*
- * folio->mlock_count = !!folio_test_mlocked(folio)?
- * But that leaves __mlock_folio() in doubt whether another
- * actor has already counted the mlock or not. Err on the
- * safe side, underestimate, let page reclaim fix it, rather
- * than leaving a page on the unevictable LRU indefinitely.
- */
- folio->mlock_count = 0;
if (!was_unevictable)
__count_vm_events(UNEVICTABLE_PGCULLED, nr_pages);
}
@@ -449,15 +430,16 @@ void folio_mark_accessed(struct folio *folio)
EXPORT_SYMBOL(folio_mark_accessed);
/**
- * folio_add_lru - Add a folio to an LRU list.
+ * __folio_add_lru - Add a folio to an LRU list.
* @folio: The folio to be added to the LRU.
+ * @mlockit: Mark the folio as mlocked.
*
* Queue the folio for addition to the LRU. The decision on whether
* to add the page to the [in]active [file|anon] list is deferred until the
* folio_batch is drained. This gives a chance for the caller of folio_add_lru()
- * have the folio added to the active list using folio_mark_accessed().
+ * to have the folio added to the active list using folio_mark_accessed().
*/
-void folio_add_lru(struct folio *folio)
+void __folio_add_lru(struct folio *folio, bool mlockit)
{
struct folio_batch *fbatch;
unsigned long lru_next;
@@ -492,6 +474,27 @@ void folio_add_lru(struct folio *folio)
lru_next |= BIT(LRU_NEXT_BATCHED);
folio->lru_next = lru_next;
+ if (mlockit) {
+ long nr_pages = folio_nr_pages(folio);
+
+ folio_set_mlocked(folio);
+ folio->mlock_count = MLOCK_COUNT_0 + MLOCK_COUNT_1;
+ zone_stat_mod_folio(folio, NR_MLOCK, nr_pages);
+ __count_vm_events(UNEVICTABLE_PGMLOCKED, nr_pages);
+ } else if (folio_test_mlocked(folio)) {
+ /*
+ * A folio is being put back while mlocked. If mlock_count
+ * has not been overwritten by use of lru.prev, believe it.
+ * Otherwise, since there may be __mlock_folio()s to come
+ * through, initialize it to the safer 0 rather than to 1.
+ */
+ if (!(folio->mlock_count & MLOCK_COUNT_0))
+ folio->mlock_count = MLOCK_COUNT_0;
+ } else {
+ /* Initialize this field, which the page allocator did not */
+ folio->mlock_count = MLOCK_COUNT_0;
+ }
+
full = !folio_batch_add(fbatch, folio);
/* Ensure folio->lru_next visible to folio_test_clear_lru() callers */
@@ -503,24 +506,20 @@ void folio_add_lru(struct folio *folio)
local_unlock(&cpu_fbatches.lock);
}
-EXPORT_SYMBOL(folio_add_lru);
+EXPORT_SYMBOL(__folio_add_lru);
/**
* folio_add_lru_vma() - Add a folio to the appropriate LRU list for this VMA.
* @folio: The folio to be added to the LRU.
* @vma: VMA in which the folio is mapped.
*
- * If the VMA is mlocked, @folio is added to the unevictable list.
+ * If the VMA is mlocked, @folio will be added to the unevictable list.
* Otherwise, it is treated the same way as folio_add_lru().
*/
void folio_add_lru_vma(struct folio *folio, struct vm_area_struct *vma)
{
- VM_BUG_ON_FOLIO(folio_test_lru(folio), folio);
-
- if (unlikely((vma->vm_flags & (VM_LOCKED | VM_SPECIAL)) == VM_LOCKED))
- mlock_new_folio(folio);
- else
- folio_add_lru(folio);
+ __folio_add_lru(folio,
+ (vma->vm_flags & (VM_LOCKED | VM_SPECIAL)) == VM_LOCKED);
}
/*
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index b1f315400111..c7bd99592195 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3621,7 +3621,7 @@ static void lru_add_split_folio(struct folio *folio, struct folio *new_folio,
/* head is still on lru (and we have it frozen) */
VM_WARN_ON(!folio_test_lru(folio));
if (folio_test_unevictable(folio))
- new_folio->mlock_count = 0;
+ new_folio->mlock_count = MLOCK_COUNT_0;
else
list_add_tail(&new_folio->lru, &folio->lru);
folio_set_lru(new_folio);
diff --git a/mm/internal.h b/mm/internal.h
index ababee1a8872..ff4bd3a14539 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -976,7 +976,7 @@ folio_within_vma(struct folio *folio, struct vm_area_struct *vma)
*
* mlock is usually called at the end of folio_add_*_rmap_*(), munlock at
* the end of folio_remove_rmap_*(); but new anon folios are managed by
- * folio_add_lru_vma() calling mlock_new_folio().
+ * folio_add_lru_vma() calling __folio_add_lru().
*/
void mlock_folio(struct folio *folio);
static inline void mlock_vma_folio(struct folio *folio,
@@ -1011,7 +1011,6 @@ static inline void munlock_vma_folio(struct folio *folio,
munlock_folio(folio);
}
-void mlock_new_folio(struct folio *folio);
bool need_mlock_drain(int cpu);
void mlock_drain_local(void);
void mlock_drain_remote(int cpu);
@@ -1105,7 +1104,6 @@ static inline bool vma_supports_mlock(const struct vm_area_struct *vma)
#else /* !CONFIG_MMU */
static inline void unmap_mapping_folio(struct folio *folio) { }
-static inline void mlock_new_folio(struct folio *folio) { }
static inline bool need_mlock_drain(int cpu) { return false; }
static inline void mlock_drain_local(void) { }
static inline void mlock_drain_remote(int cpu) { }
diff --git a/mm/mlock.c b/mm/mlock.c
index 14c02e155d68..53d754e82ba2 100644
--- a/mm/mlock.c
+++ b/mm/mlock.c
@@ -85,14 +85,16 @@ static struct lruvec *__mlock_folio(struct folio *folio, struct lruvec *lruvec)
if (folio_test_unevictable(folio)) {
if (folio_test_mlocked(folio))
- folio->mlock_count++;
+ folio->mlock_count += MLOCK_COUNT_1;
goto out;
}
lruvec_del_folio(lruvec, folio);
folio_clear_active(folio);
folio_set_unevictable(folio);
- folio->mlock_count = !!folio_test_mlocked(folio);
+ folio->mlock_count = MLOCK_COUNT_0;
+ if (folio_test_mlocked(folio))
+ folio->mlock_count += MLOCK_COUNT_1;
lruvec_add_folio(lruvec, folio);
__count_vm_events(UNEVICTABLE_PGCULLED, folio_nr_pages(folio));
out:
@@ -100,25 +102,6 @@ static struct lruvec *__mlock_folio(struct folio *folio, struct lruvec *lruvec)
return lruvec;
}
-static struct lruvec *__mlock_new_folio(struct folio *folio, struct lruvec *lruvec)
-{
- VM_BUG_ON_FOLIO(folio_test_lru(folio), folio);
-
- lruvec = folio_lruvec_relock_irq(folio, lruvec);
-
- /* As above, this is a little surprising, but possible */
- if (unlikely(folio_evictable(folio)))
- goto out;
-
- folio_set_unevictable(folio);
- folio->mlock_count = !!folio_test_mlocked(folio);
- __count_vm_events(UNEVICTABLE_PGCULLED, folio_nr_pages(folio));
-out:
- lruvec_add_folio(lruvec, folio);
- folio_set_lru(folio);
- return lruvec;
-}
-
static struct lruvec *__munlock_folio(struct folio *folio, struct lruvec *lruvec)
{
int nr_pages = folio_nr_pages(folio);
@@ -132,9 +115,9 @@ static struct lruvec *__munlock_folio(struct folio *folio, struct lruvec *lruvec
if (folio_test_unevictable(folio)) {
/* Then mlock_count is maintained, but might undercount */
- if (folio->mlock_count)
- folio->mlock_count--;
- if (folio->mlock_count)
+ if (folio->mlock_count > MLOCK_COUNT_0)
+ folio->mlock_count -= MLOCK_COUNT_1;
+ if (folio->mlock_count > MLOCK_COUNT_0)
goto out;
}
/* else assume that was the last mlock: reclaim will fix it if not */
@@ -165,17 +148,11 @@ static struct lruvec *__munlock_folio(struct folio *folio, struct lruvec *lruvec
* Flags held in the low bits of a struct folio pointer on the mlock_fbatch.
*/
#define LRU_FOLIO 0x1
-#define NEW_FOLIO 0x2
static inline struct folio *mlock_lru(struct folio *folio)
{
return (struct folio *)((unsigned long)folio + LRU_FOLIO);
}
-static inline struct folio *mlock_new(struct folio *folio)
-{
- return (struct folio *)((unsigned long)folio + NEW_FOLIO);
-}
-
/*
* mlock_folio_batch() is derived from folio_batch_move_lru(): perhaps that can
* make use of such folio pointer flags in future, but for now just keep it for
@@ -192,14 +169,12 @@ static void mlock_folio_batch(struct folio_batch *fbatch)
for (i = 0; i < folio_batch_count(fbatch); i++) {
folio = fbatch->folios[i];
- mlock = (unsigned long)folio & (LRU_FOLIO | NEW_FOLIO);
+ mlock = (unsigned long)folio & LRU_FOLIO;
folio = (struct folio *)((unsigned long)folio - mlock);
fbatch->folios[i] = folio;
- if (mlock & LRU_FOLIO)
+ if (mlock)
lruvec = __mlock_folio(folio, lruvec);
- else if (mlock & NEW_FOLIO)
- lruvec = __mlock_new_folio(folio, lruvec);
else
lruvec = __munlock_folio(folio, lruvec);
}
@@ -261,30 +236,6 @@ void mlock_folio(struct folio *folio)
local_unlock(&mlock_fbatch.lock);
}
-/**
- * mlock_new_folio - mlock a newly allocated folio not yet on LRU
- * @folio: folio to be mlocked, either normal or a THP head.
- */
-void mlock_new_folio(struct folio *folio)
-{
- struct folio_batch *fbatch;
- int nr_pages = folio_nr_pages(folio);
-
- local_lock(&mlock_fbatch.lock);
- fbatch = this_cpu_ptr(&mlock_fbatch.fbatch);
- folio_set_mlocked(folio);
-
- zone_stat_mod_folio(folio, NR_MLOCK, nr_pages);
- __count_vm_events(UNEVICTABLE_PGMLOCKED, nr_pages);
-
- folio_get(folio);
- if (!folio_batch_add(fbatch, mlock_new(folio)) ||
- true || /* XXX Temporarily disable mlock_new batching */
- !folio_may_be_lru_cached(folio) || lru_cache_disabled())
- mlock_folio_batch(fbatch);
- local_unlock(&mlock_fbatch.lock);
-}
-
/**
* munlock_folio - munlock a folio
* @folio: folio to be munlocked, either normal or a THP head.
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH 09/25] mm/fbatch: restore mlock+munlock batching, without extra ref
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
` (7 preceding siblings ...)
2026-08-24 14:11 ` [PATCH 08/25] mm/fbatch: replace mlock_new_folio() by __folio_add_lru(,mlockit) Hugh Dickins
@ 2026-08-24 14:14 ` Hugh Dickins
2026-08-24 14:16 ` [PATCH 10/25] mm/fbatch: remove several uses of mlock_drain_local() Hugh Dickins
` (15 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 14:14 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
Update mlock_folio(), munlock_folio() and their fbatch callouts and
helpers, to do folio_try_get()s at batch processing time, instead of
holding a folio reference all the while in mlock_fbatch: as in folio.c.
But more interesting is the use of mod_mlock_count(), using try_cmpxchg()
to update folio->mlock_count safely when possible (now when on lru_add
fbatch as well as when unevictable). While __mlock_folio() is as hard to
think about as before, __munlock_folio() simpler because munlock_folio()
can adjust mlock_count itself without clear_lru() or lruvec lock, and so
do the folio_test_clear_mlocked() immediately for itself (without which
unevictable_pgs_cleared was likely to appear high, when it should be 0
or low to indicate good mlock health).
__munlock_folio() is safe for use even when the unreferenced folio has
been freed and reused. It appears that __mlock_folio() could affect a
folio which has been freed and reused, but only if it is reused as an
mlocked folio, in which case its mlock_count is spuriously incremented
(but usually a spurious munlock decrement will follow). How grave is
this? If unevictable_pgs_cleared remains low, not so bad.
I've gone back and forth on whether to move mlock_fbatch and these
functions into mm/folio.c: for now they stay here in mm/mlock.c.
Signed-off-by: Hugh Dickins <hughd@google.com>
---
mm/mlock.c | 147 +++++++++++++++++++++++++++++++----------------------
1 file changed, 86 insertions(+), 61 deletions(-)
diff --git a/mm/mlock.c b/mm/mlock.c
index 53d754e82ba2..1050010bbe0b 100644
--- a/mm/mlock.c
+++ b/mm/mlock.c
@@ -58,6 +58,20 @@ EXPORT_SYMBOL(can_do_mlock);
* indicate the unevictable state.
*/
+static long mod_mlock_count(struct folio *folio, long incdec)
+{
+ long mlock_count = READ_ONCE(folio->mlock_count);
+
+ while (mlock_count & MLOCK_COUNT_0) {
+ if (mlock_count + incdec < MLOCK_COUNT_0)
+ return MLOCK_COUNT_0;
+ if (try_cmpxchg(&folio->mlock_count, &mlock_count,
+ mlock_count + incdec))
+ return mlock_count + incdec;
+ }
+ return 0;
+}
+
static struct lruvec *__mlock_folio(struct folio *folio, struct lruvec *lruvec)
{
/* There is nothing more we can do while it's off LRU */
@@ -65,6 +79,7 @@ static struct lruvec *__mlock_folio(struct folio *folio, struct lruvec *lruvec)
return lruvec;
lruvec = folio_lruvec_relock_irq(folio, lruvec);
+ lruvec_del_folio(lruvec, folio);
if (unlikely(folio_evictable(folio))) {
/*
@@ -73,92 +88,82 @@ static struct lruvec *__mlock_folio(struct folio *folio, struct lruvec *lruvec)
* folio be unevictable? I'm not sure, but move it now if so.
*/
if (folio_test_unevictable(folio)) {
- lruvec_del_folio(lruvec, folio);
folio_clear_unevictable(folio);
- lruvec_add_folio(lruvec, folio);
-
__count_vm_events(UNEVICTABLE_PGRESCUED,
folio_nr_pages(folio));
}
goto out;
}
+ /*
+ * Something to keep in mind when studying the arithmetic here:
+ * we only come to __mlock_folio() when mlock_folio() could not
+ * mod_mlock_count() itself; but by the time this is processed,
+ * the folio may have already been munlocked, or another mlock
+ * already marked it as unevictable and so mod_mlock_countable.
+ * And don't forget that a folio may be unevictable for reasons
+ * other than mlocked (hence the folio_evictable() check above).
+ */
+
if (folio_test_unevictable(folio)) {
if (folio_test_mlocked(folio))
- folio->mlock_count += MLOCK_COUNT_1;
+ mod_mlock_count(folio, MLOCK_COUNT_1);
goto out;
}
- lruvec_del_folio(lruvec, folio);
folio_clear_active(folio);
folio_set_unevictable(folio);
- folio->mlock_count = MLOCK_COUNT_0;
- if (folio_test_mlocked(folio))
- folio->mlock_count += MLOCK_COUNT_1;
- lruvec_add_folio(lruvec, folio);
__count_vm_events(UNEVICTABLE_PGCULLED, folio_nr_pages(folio));
+
+ if (!folio_test_mlocked(folio))
+ folio->mlock_count = MLOCK_COUNT_0;
+ else if (!mod_mlock_count(folio, MLOCK_COUNT_1))
+ folio->mlock_count = MLOCK_COUNT_0 + MLOCK_COUNT_1;
out:
+ lruvec_add_folio(lruvec, folio);
folio_set_lru(folio);
return lruvec;
}
static struct lruvec *__munlock_folio(struct folio *folio, struct lruvec *lruvec)
{
- int nr_pages = folio_nr_pages(folio);
- bool isolated = false;
+ long nr_pages = folio_nr_pages(folio);
- if (!folio_test_clear_lru(folio))
- goto munlock;
-
- isolated = true;
- lruvec = folio_lruvec_relock_irq(folio, lruvec);
-
- if (folio_test_unevictable(folio)) {
- /* Then mlock_count is maintained, but might undercount */
- if (folio->mlock_count > MLOCK_COUNT_0)
- folio->mlock_count -= MLOCK_COUNT_1;
- if (folio->mlock_count > MLOCK_COUNT_0)
- goto out;
- }
- /* else assume that was the last mlock: reclaim will fix it if not */
-
-munlock:
- if (folio_test_clear_mlocked(folio)) {
- __zone_stat_mod_folio(folio, NR_MLOCK, -nr_pages);
- if (isolated || !folio_test_unevictable(folio))
- __count_vm_events(UNEVICTABLE_PGMUNLOCKED, nr_pages);
- else
+ /* There is nothing more we can do while it's off LRU */
+ if (!folio_test_clear_lru(folio)) {
+ if (folio_test_unevictable(folio) && folio_evictable(folio))
__count_vm_events(UNEVICTABLE_PGSTRANDED, nr_pages);
+ /* But whoever puts it back on LRU should rescue it */
+ return lruvec;
}
- /* folio_evictable() has to be checked *after* clearing Mlocked */
- if (isolated && folio_test_unevictable(folio) && folio_evictable(folio)) {
- lruvec_del_folio(lruvec, folio);
+ lruvec = folio_lruvec_relock_irq(folio, lruvec);
+ lruvec_del_folio(lruvec, folio);
+
+ if (folio_test_unevictable(folio) && folio_evictable(folio)) {
folio_clear_unevictable(folio);
- lruvec_add_folio(lruvec, folio);
__count_vm_events(UNEVICTABLE_PGRESCUED, nr_pages);
}
-out:
- if (isolated)
- folio_set_lru(folio);
+
+ lruvec_add_folio(lruvec, folio);
+ folio_set_lru(folio);
return lruvec;
}
/*
- * Flags held in the low bits of a struct folio pointer on the mlock_fbatch.
+ * Flag held in the low bits of a struct folio pointer on the mlock_fbatch.
*/
-#define LRU_FOLIO 0x1
-static inline struct folio *mlock_lru(struct folio *folio)
+#define MLOCK_FLAG 0x1
+static inline struct folio *mlock_flagged(struct folio *folio)
{
- return (struct folio *)((unsigned long)folio + LRU_FOLIO);
+ return (struct folio *)((unsigned long)folio + MLOCK_FLAG);
}
/*
* mlock_folio_batch() is derived from folio_batch_move_lru(): perhaps that can
* make use of such folio pointer flags in future, but for now just keep it for
- * mlock. We could use three separate folio batches instead, but one feels
- * better (munlocking a full folio batch does not need to drain mlocking folio
- * batches first).
+ * mlock. We could use separate folio batches instead, but one feels better
+ * (munlocking a full folio batch does not need to drain mlocking batch first).
*/
static void mlock_folio_batch(struct folio_batch *fbatch)
{
@@ -169,10 +174,15 @@ static void mlock_folio_batch(struct folio_batch *fbatch)
for (i = 0; i < folio_batch_count(fbatch); i++) {
folio = fbatch->folios[i];
- mlock = (unsigned long)folio & LRU_FOLIO;
+ mlock = (unsigned long)folio & MLOCK_FLAG;
folio = (struct folio *)((unsigned long)folio - mlock);
fbatch->folios[i] = folio;
+ if (!folio_try_get(folio)) {
+ fbatch->folios[i] = NULL;
+ continue;
+ }
+
if (mlock)
lruvec = __mlock_folio(folio, lruvec);
else
@@ -218,19 +228,25 @@ void mlock_folio(struct folio *folio)
{
struct folio_batch *fbatch;
- local_lock(&mlock_fbatch.lock);
- fbatch = this_cpu_ptr(&mlock_fbatch.fbatch);
-
if (!folio_test_set_mlocked(folio)) {
- int nr_pages = folio_nr_pages(folio);
+ long nr_pages = folio_nr_pages(folio);
zone_stat_mod_folio(folio, NR_MLOCK, nr_pages);
- __count_vm_events(UNEVICTABLE_PGMLOCKED, nr_pages);
+ count_vm_events(UNEVICTABLE_PGMLOCKED, nr_pages);
}
- folio_get(folio);
- if (!folio_batch_add(fbatch, mlock_lru(folio)) ||
- true || /* XXX Temporarily disable mlock batching */
+ /*
+ * No more to do if mlock_count is maintained: either the folio
+ * is on an lru_add fbatch, and will be moved to unevictable in
+ * due course, or it's already counted as unevictable: no need
+ * for an mlock_fbatch entry below.
+ */
+ if (mod_mlock_count(folio, MLOCK_COUNT_1))
+ return;
+
+ local_lock(&mlock_fbatch.lock);
+ fbatch = this_cpu_ptr(&mlock_fbatch.fbatch);
+ if (!folio_batch_add(fbatch, mlock_flagged(folio)) ||
!folio_may_be_lru_cached(folio) || lru_cache_disabled())
mlock_folio_batch(fbatch);
local_unlock(&mlock_fbatch.lock);
@@ -244,15 +260,24 @@ void munlock_folio(struct folio *folio)
{
struct folio_batch *fbatch;
+ /*
+ * No more to do if mlock_count is maintained and still raised.
+ * But if mlock_count is unmaintained, we might need to queue an
+ * munlock fbatch entry, just to cancel an undequeued mlock entry?
+ */
+ if (mod_mlock_count(folio, -MLOCK_COUNT_1) > MLOCK_COUNT_0)
+ return;
+
+ if (folio_test_clear_mlocked(folio)) {
+ long nr_pages = folio_nr_pages(folio);
+
+ zone_stat_mod_folio(folio, NR_MLOCK, -nr_pages);
+ count_vm_events(UNEVICTABLE_PGMUNLOCKED, nr_pages);
+ }
+
local_lock(&mlock_fbatch.lock);
fbatch = this_cpu_ptr(&mlock_fbatch.fbatch);
- /*
- * folio_test_clear_mlocked(folio) must be left to __munlock_folio(),
- * which will check whether the folio is multiply mlocked.
- */
- folio_get(folio);
if (!folio_batch_add(fbatch, folio) ||
- true || /* XXX Temporarily disable munlock batching */
!folio_may_be_lru_cached(folio) || lru_cache_disabled())
mlock_folio_batch(fbatch);
local_unlock(&mlock_fbatch.lock);
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH 10/25] mm/fbatch: remove several uses of mlock_drain_local()
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
` (8 preceding siblings ...)
2026-08-24 14:14 ` [PATCH 09/25] mm/fbatch: restore mlock+munlock batching, without extra ref Hugh Dickins
@ 2026-08-24 14:16 ` Hugh Dickins
2026-08-24 14:18 ` [PATCH 11/25] mm/fbatch: remove migration's PAGE_WAS_MLOCKED lru_add_drain() Hugh Dickins
` (14 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 14:16 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
Remove several uses of mlock_drain_local(): they were workarounds for
when that sequence of operations tended to leave folios in mlock_fbatch
with debilitating raised refcount (and no good if the task changed CPU).
Signed-off-by: Hugh Dickins <hughd@google.com>
---
mm/huge_memory.c | 2 --
mm/migrate.c | 2 --
mm/rmap.c | 4 ----
3 files changed, 8 deletions(-)
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index c7bd99592195..df6bf69a066b 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3568,8 +3568,6 @@ static bool __discard_anon_folio_pmd_locked(struct vm_area_struct *vma,
folio_remove_rmap_pmd(folio, pmd_page(orig_pmd), vma);
zap_deposited_table(mm, pmdp);
add_mm_counter(mm, MM_ANONPAGES, -HPAGE_PMD_NR);
- if (vma->vm_flags & VM_LOCKED)
- mlock_drain_local();
folio_put(folio);
return true;
diff --git a/mm/migrate.c b/mm/migrate.c
index 8aaafcea7bc1..6b71b2415d00 100644
--- a/mm/migrate.c
+++ b/mm/migrate.c
@@ -448,8 +448,6 @@ static bool remove_migration_pte(struct folio *folio,
folio_add_file_rmap_pte(folio, new, vma);
set_pte_at(vma->vm_mm, pvmw.address, pvmw.pte, pte);
}
- if (READ_ONCE(vma->vm_flags) & VM_LOCKED)
- mlock_drain_local();
trace_remove_migration_pte(pvmw.address, pte_val(pte),
compound_order(new));
diff --git a/mm/rmap.c b/mm/rmap.c
index 1f72d279ba68..e76823ec4d5e 100644
--- a/mm/rmap.c
+++ b/mm/rmap.c
@@ -2390,8 +2390,6 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,
}
finish_unmap:
folio_remove_rmap_ptes(folio, page, nr_pages, vma);
- if (vma->vm_flags & VM_LOCKED)
- mlock_drain_local();
folio_put_refs(folio, nr_pages);
/*
@@ -2765,8 +2763,6 @@ static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma,
hugetlb_remove_rmap(folio);
else
folio_remove_rmap_pte(folio, subpage, vma);
- if (vma->vm_flags & VM_LOCKED)
- mlock_drain_local();
folio_put(folio);
}
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH 11/25] mm/fbatch: remove migration's PAGE_WAS_MLOCKED lru_add_drain()
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
` (9 preceding siblings ...)
2026-08-24 14:16 ` [PATCH 10/25] mm/fbatch: remove several uses of mlock_drain_local() Hugh Dickins
@ 2026-08-24 14:18 ` Hugh Dickins
2026-08-24 14:20 ` [PATCH 12/25] mm/fbatch: remove percpu_pvec_drained and folios_put() Hugh Dickins
` (13 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 14:18 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
A welcome side-effect of mm/mlock.c's mod_mlock_count() succeeding on
folios on the per-cpu lru_add fbatch, is that migrate_folio_move() no
longer has to lru_add_drain() before remove_migration_ptes() restores
a PAGE_WAS_MLOCKED mlock_count: so remove PAGE_WAS_MLOCKED altogether.
Re the "We would like to do something similar for the old page, when
unsuccessful" comment above it: that may be easier now, but involve
some rearrangement: not researched, so just leave the comment as is.
Signed-off-by: Hugh Dickins <hughd@google.com>
---
mm/migrate.c | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/mm/migrate.c b/mm/migrate.c
index 6b71b2415d00..534908a0839a 100644
--- a/mm/migrate.c
+++ b/mm/migrate.c
@@ -1148,8 +1148,7 @@ static int move_to_new_folio(struct folio *dst, struct folio *src,
*/
enum {
FOLIO_WAS_MAPPED = BIT(0),
- FOLIO_WAS_MLOCKED = BIT(1),
- FOLIO_OLD_STATES = FOLIO_WAS_MAPPED | FOLIO_WAS_MLOCKED,
+ FOLIO_OLD_STATES = FOLIO_WAS_MAPPED,
};
static void __migrate_folio_record(struct folio *dst,
@@ -1259,8 +1258,6 @@ static int migrate_folio_unmap(new_folio_t get_new_folio,
folio_lock(src);
}
locked = true;
- if (folio_test_mlocked(src))
- old_folio_state |= FOLIO_WAS_MLOCKED;
if (folio_test_writeback(src)) {
/*
@@ -1411,9 +1408,6 @@ static int migrate_folio_move(free_folio_t put_new_folio, unsigned long private,
* isolated from the unevictable LRU: but this case is the easiest.
*/
folio_add_lru(dst);
- if (old_folio_state & FOLIO_WAS_MLOCKED)
- lru_add_drain();
-
if (old_folio_state & FOLIO_WAS_MAPPED)
remove_migration_ptes(src, dst, 0);
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH 12/25] mm/fbatch: remove percpu_pvec_drained and folios_put()
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
` (10 preceding siblings ...)
2026-08-24 14:18 ` [PATCH 11/25] mm/fbatch: remove migration's PAGE_WAS_MLOCKED lru_add_drain() Hugh Dickins
@ 2026-08-24 14:20 ` Hugh Dickins
2026-08-24 14:23 ` [PATCH 13/25] mm/fbatch: no lru_add_drain() to collect_longterm_unpinnable_folios() Hugh Dickins
` (12 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 14:20 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
Remove the percpu_pvec_drained field from folio_batch, and its only use
in __folio_batch_release(): remove that now pointless lru_add_drain().
Which leaves __folio_batch_release() as an exported name for folios_put()
which is itself just a wrapper for folios_put_refs(): mm/mlock.c and
mm/folio.c don't need such a wrapper, just say folios_put_refs(,NULL).
Or should folios_put() be the export? But __folio_batch_release() is what
drivers/gpu and net/sunrpc are using: don't change them in this series.
Signed-off-by: Hugh Dickins <hughd@google.com>
---
include/linux/folio_batch.h | 2 --
include/linux/mm.h | 18 ------------------
mm/folio.c | 17 +++--------------
mm/mlock.c | 2 +-
4 files changed, 4 insertions(+), 35 deletions(-)
diff --git a/include/linux/folio_batch.h b/include/linux/folio_batch.h
index e1cc8ae023f1..a3337f70e109 100644
--- a/include/linux/folio_batch.h
+++ b/include/linux/folio_batch.h
@@ -27,7 +27,6 @@ struct folio;
struct folio_batch {
unsigned char nr;
unsigned char i;
- bool percpu_pvec_drained;
struct folio *folios[FOLIO_BATCH_SIZE];
};
@@ -41,7 +40,6 @@ static inline void folio_batch_init(struct folio_batch *fbatch)
{
fbatch->nr = 0;
fbatch->i = 0;
- fbatch->percpu_pvec_drained = false;
}
static inline void folio_batch_reinit(struct folio_batch *fbatch)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 87feaa5a2b78..a426f7351787 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -2201,24 +2201,6 @@ typedef union {
void release_pages(release_pages_arg, int nr);
-/**
- * folios_put - Decrement the reference count on an array of folios.
- * @folios: The folios.
- *
- * Like folio_put(), but for a batch of folios. This is more efficient
- * than writing the loop yourself as it will optimise the locks which need
- * to be taken if the folios are freed. The folios batch is returned
- * empty and ready to be reused for another batch; there is no need to
- * reinitialise it.
- *
- * Context: May be called in process or interrupt context, but not in NMI
- * context. May be called while holding a spinlock.
- */
-static inline void folios_put(struct folio_batch *folios)
-{
- folios_put_refs(folios, NULL);
-}
-
static inline void put_page(struct page *page)
{
struct folio *folio = page_folio(page);
diff --git a/mm/folio.c b/mm/folio.c
index fa4cf9d7d51b..782b8245d213 100644
--- a/mm/folio.c
+++ b/mm/folio.c
@@ -159,7 +159,7 @@ static void folio_batch_move_lru(struct folio_batch *fbatch, move_fn_t move_fn)
if (lruvec)
lruvec_unlock_irqrestore(lruvec, flags);
- folios_put(fbatch);
+ folios_put_refs(fbatch, NULL);
}
static void __folio_batch_add_and_move(struct folio_batch __percpu *fbatch,
@@ -1062,22 +1062,11 @@ void release_pages(release_pages_arg arg, int nr)
EXPORT_SYMBOL(release_pages);
/*
- * The folios which we're about to release may be in the deferred lru-addition
- * queues. That would prevent them from really being freed right now. That's
- * OK from a correctness point of view but is inefficient - those folios may be
- * cache-warm and we want to give them back to the page allocator ASAP.
- *
- * So __folio_batch_release() will drain those queues here.
- * folio_batch_move_lru() calls folios_put() directly to avoid
- * mutual recursion.
+ * This used to optimize with a drain before putting: no longer helpful.
*/
void __folio_batch_release(struct folio_batch *fbatch)
{
- if (!fbatch->percpu_pvec_drained) {
- lru_add_drain();
- fbatch->percpu_pvec_drained = true;
- }
- folios_put(fbatch);
+ folios_put_refs(fbatch, NULL);
}
EXPORT_SYMBOL(__folio_batch_release);
diff --git a/mm/mlock.c b/mm/mlock.c
index 1050010bbe0b..97134eff6b56 100644
--- a/mm/mlock.c
+++ b/mm/mlock.c
@@ -191,7 +191,7 @@ static void mlock_folio_batch(struct folio_batch *fbatch)
if (lruvec)
lruvec_unlock_irq(lruvec);
- folios_put(fbatch);
+ folios_put_refs(fbatch, NULL);
}
void mlock_drain_local(void)
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH 13/25] mm/fbatch: no lru_add_drain() to collect_longterm_unpinnable_folios()
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
` (11 preceding siblings ...)
2026-08-24 14:20 ` [PATCH 12/25] mm/fbatch: remove percpu_pvec_drained and folios_put() Hugh Dickins
@ 2026-08-24 14:23 ` Hugh Dickins
2026-08-24 14:55 ` [PATCH alt " Hugh Dickins
2026-08-24 14:25 ` [PATCH 14/25] mm/fbatch: no lru_add_drain() nor _all() for memfd_wait_for_pins() Hugh Dickins
` (11 subsequent siblings)
24 siblings, 1 reply; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 14:23 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
collect_longterm_unpinnable_folios() has no use for lru_add_drain() nor
lru_add_drain_all(), now that the per-cpu fbatch references are gone.
Signed-off-by: Hugh Dickins <hughd@google.com>
---
mm/gup.c | 14 --------------
1 file changed, 14 deletions(-)
diff --git a/mm/gup.c b/mm/gup.c
index 99902c15703b..9616379ab069 100644
--- a/mm/gup.c
+++ b/mm/gup.c
@@ -2268,7 +2268,6 @@ static unsigned long collect_longterm_unpinnable_folios(
{
unsigned long collected = 0;
struct folio *folio;
- int drained = 0;
long i = 0;
for (folio = pofs_get_folio(pofs, i); folio;
@@ -2287,19 +2286,6 @@ static unsigned long collect_longterm_unpinnable_folios(
continue;
}
- if (drained == 0 && folio_may_be_lru_cached(folio) &&
- folio_ref_count(folio) !=
- folio_expected_ref_count(folio) + 1) {
- lru_add_drain();
- drained = 1;
- }
- if (drained == 1 && folio_may_be_lru_cached(folio) &&
- folio_ref_count(folio) !=
- folio_expected_ref_count(folio) + 1) {
- lru_add_drain_all();
- drained = 2;
- }
-
if (!folio_isolate_lru(folio))
continue;
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH alt 13/25] mm/fbatch: no lru_add_drain() to collect_longterm_unpinnable_folios()
2026-08-24 14:23 ` [PATCH 13/25] mm/fbatch: no lru_add_drain() to collect_longterm_unpinnable_folios() Hugh Dickins
@ 2026-08-24 14:55 ` Hugh Dickins
2026-08-24 18:42 ` David Hildenbrand (Arm)
0 siblings, 1 reply; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 14:55 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
collect_longterm_unpinnable_folios() has no use for lru_add_drain() nor
lru_add_drain_all(), now that the per-cpu fbatch references are gone.
So remove the recently added lru_cache_drain_for_folio().
Signed-off-by: Hugh Dickins <hughd@google.com>
---
Alternate version for use once David Hildenbrand's two commits
here have been pulled from mm.git into linux.git for 7.3-rc1.
include/linux/swap.h | 8 --------
mm/folio.c | 46 --------------------------------------------
mm/gup.c | 9 ---------
3 files changed, 63 deletions(-)
diff --git a/include/linux/swap.h b/include/linux/swap.h
index 17c60956c553..ecb877fe61ed 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -302,14 +302,6 @@ static inline void folio_add_lru(struct folio *folio)
void folio_mark_accessed(struct folio *folio);
void lru_add_drain_all(void);
-enum lru_cache_drained {
- LRU_CACHE_NOT_DRAINED,
- LRU_CACHE_DRAINED,
- LRU_CACHE_DRAINED_ALL,
-};
-void lru_cache_drain_for_folio(const struct folio *folio,
- unsigned int extra_refs, enum lru_cache_drained *drained);
-
/* linux/mm/folio-compat.c */
void mark_page_accessed(struct page *page);
diff --git a/mm/folio.c b/mm/folio.c
index 1c03b44362c1..7b309059b302 100644
--- a/mm/folio.c
+++ b/mm/folio.c
@@ -842,52 +842,6 @@ void lru_add_drain_all(void)
}
#endif /* CONFIG_SMP */
-/**
- * lru_cache_drain_for_folio() - drain LRU caches if the caches might hold
- * folio references
- * @folio: The folio.
- * @extra_refs: Extra folio references held by the caller.
- * @drained: Drain status for batch folio processing.
- *
- * Drain LRU caches if the caches might hold folio references. Start
- * with a local LRU cache drain, to then drain LRU caches on all CPUs if
- * local draining was insufficient.
- *
- * This function detects LRU cache references by comparing the folio refcount
- * with the sum of the expected folio refcount + extra references held by the
- * caller. Note that we cannot rely on PG_lru to reliably detect all LRU
- * cache references, and there are rare scenarios (concurrent folio (un)mapping)
- * where this function might miss detecting LRU cache references.
- *
- * If @drained is not NULL, the function will avoid re-draining LRU caches
- * when processing multiple folios in a row. In that case, the variable
- * @drained points at must be initialized to LRU_CACHE_NOT_DRAINED before
- * the first invocation by the caller.
- */
-void lru_cache_drain_for_folio(const struct folio *folio,
- unsigned int extra_refs, enum lru_cache_drained *drained)
-{
- if (!folio_may_be_lru_cached(folio))
- return;
-
- if (!drained || *drained == LRU_CACHE_NOT_DRAINED) {
- if (folio_ref_count(folio) ==
- folio_expected_ref_count(folio) + extra_refs)
- return;
- lru_add_drain();
- if (drained)
- *drained = LRU_CACHE_DRAINED;
- }
- if (!drained || *drained == LRU_CACHE_DRAINED) {
- if (folio_ref_count(folio) ==
- folio_expected_ref_count(folio) + extra_refs)
- return;
- lru_add_drain_all();
- if (drained)
- *drained = LRU_CACHE_DRAINED_ALL;
- }
-}
-
atomic_t lru_disable_count = ATOMIC_INIT(0);
/*
diff --git a/mm/gup.c b/mm/gup.c
index 98fdf7b7baca..e87a77dacdb6 100644
--- a/mm/gup.c
+++ b/mm/gup.c
@@ -2273,14 +2273,12 @@ static unsigned long collect_longterm_unpinnable_folios(
struct list_head *movable_folio_list,
struct pages_or_folios *pofs)
{
- enum lru_cache_drained drained = LRU_CACHE_NOT_DRAINED;
unsigned long collected = 0;
struct folio *folio;
long i = 0;
for (folio = pofs_get_folio(pofs, i); folio;
folio = pofs_next_folio(folio, pofs, &i)) {
- const int pin_refs = folio_has_pincount(folio) ? 1 : GUP_PIN_COUNTING_BIAS;
if (folio_is_longterm_pinnable(folio))
continue;
@@ -2295,13 +2293,6 @@ static unsigned long collect_longterm_unpinnable_folios(
continue;
}
- /*
- * We drain not only to make the folio_isolate_lru() succeed,
- * but also to remove any other folio references from LRU
- * caches.
- */
- lru_cache_drain_for_folio(folio, pin_refs, &drained);
-
if (!folio_isolate_lru(folio))
continue;
^ permalink raw reply [flat|nested] 28+ messages in thread* Re: [PATCH alt 13/25] mm/fbatch: no lru_add_drain() to collect_longterm_unpinnable_folios()
2026-08-24 14:55 ` [PATCH alt " Hugh Dickins
@ 2026-08-24 18:42 ` David Hildenbrand (Arm)
0 siblings, 0 replies; 28+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-24 18:42 UTC (permalink / raw)
To: Hugh Dickins, Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, JP Kobryn, Jan Kara, Jens Axboe,
Johannes Weiner, Kairui Song, Kiryl Shutsemau, Lance Yang,
Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti, Matthew Wilcox,
Mel Gorman, Miaohe Lin, Michal Hocko, Minchan Kim, Muchun Song,
Oscar Salvador, Peter Zijlstra, Qi Zheng, Rik van Riel,
Sebastian Andrzej Siewior, Shakeel Butt, Suren Baghdasaryan,
Vlastimil Babka, Yang Shi, Yu Zhao, Zach O'Keefe, Zi Yan,
linux-block, linux-fsdevel, linux-kernel, linux-mm
On 8/24/26 16:55, Hugh Dickins wrote:
> collect_longterm_unpinnable_folios() has no use for lru_add_drain() nor
> lru_add_drain_all(), now that the per-cpu fbatch references are gone.
> So remove the recently added lru_cache_drain_for_folio().
>
> Signed-off-by: Hugh Dickins <hughd@google.com>
> ---
> Alternate version for use once David Hildenbrand's two commits
> here have been pulled from mm.git into linux.git for 7.3-rc1.
For 7.4 we expect another user of the helper in KVM guest_memfd code (the
primary reason why we introduce the helper), so depending on when this series is
ready it will just remove that user as well, or we'll need some coordination
between trees.
--
Cheers,
David
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH 14/25] mm/fbatch: no lru_add_drain() nor _all() for memfd_wait_for_pins()
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
` (12 preceding siblings ...)
2026-08-24 14:23 ` [PATCH 13/25] mm/fbatch: no lru_add_drain() to collect_longterm_unpinnable_folios() Hugh Dickins
@ 2026-08-24 14:25 ` Hugh Dickins
2026-08-24 14:27 ` [PATCH 15/25] mm/fbatch: remove shake_folio() shake_page() from memory-failure Hugh Dickins
` (10 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 14:25 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
memfd_tag_pins() used lru_add_drain(), memfd_wait_for_pins() then used
lru_add_drain_all(): remove those calls, no longer useful now that the
per-cpu fbatch references are gone.
Signed-off-by: Hugh Dickins <hughd@google.com>
---
mm/memfd.c | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/mm/memfd.c b/mm/memfd.c
index c708d92533f4..064f24dcddaa 100644
--- a/mm/memfd.c
+++ b/mm/memfd.c
@@ -40,8 +40,6 @@ static void memfd_tag_pins(struct xa_state *xas)
struct folio *folio;
int latency = 0;
- lru_add_drain();
-
xas_lock_irq(xas);
xas_for_each(xas, folio, ULONG_MAX) {
if (!xa_is_value(folio) && memfd_folio_has_extra_refs(folio))
@@ -168,9 +166,7 @@ static int memfd_wait_for_pins(struct address_space *mapping)
if (!xas_marked(&xas, MEMFD_TAG_PINNED))
break;
- if (!scan)
- lru_add_drain_all();
- else if (schedule_timeout_killable((HZ << scan) / 200))
+ if (scan && schedule_timeout_killable((HZ << scan) / 200))
scan = LAST_SCAN;
xas_set(&xas, 0);
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH 15/25] mm/fbatch: remove shake_folio() shake_page() from memory-failure
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
` (13 preceding siblings ...)
2026-08-24 14:25 ` [PATCH 14/25] mm/fbatch: no lru_add_drain() nor _all() for memfd_wait_for_pins() Hugh Dickins
@ 2026-08-24 14:27 ` Hugh Dickins
2026-08-24 14:30 ` [PATCH 16/25] mm/fbatch: remove lru_cache_disable(() from NUMA folio migration Hugh Dickins
` (9 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 14:27 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
shake_folio()'s lru_add_drain_all() no longer serves a purpose, now that
the per-cpu fbatch references are gone. Are the retries in get_any_page()
then still useful? Not obvious, so keep them.
Signed-off-by: Hugh Dickins <hughd@google.com>
---
mm/hwpoison-inject.c | 1 -
mm/internal.h | 1 -
mm/memory-failure.c | 38 +-------------------------------------
3 files changed, 1 insertion(+), 39 deletions(-)
diff --git a/mm/hwpoison-inject.c b/mm/hwpoison-inject.c
index a11222572f97..9eab4b7d25b2 100644
--- a/mm/hwpoison-inject.c
+++ b/mm/hwpoison-inject.c
@@ -118,7 +118,6 @@ static int hwpoison_inject(void *data, u64 val)
if (!hwpoison_filter_enable)
goto inject;
- shake_folio(folio);
/*
* This implies unable to support non-LRU pages except free page.
*/
diff --git a/mm/internal.h b/mm/internal.h
index ff4bd3a14539..9a25552cbd83 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -1140,7 +1140,6 @@ static inline bool node_reclaim_enabled(void)
*/
#ifdef CONFIG_MEMORY_FAILURE
int unmap_poisoned_folio(struct folio *folio, unsigned long pfn, bool must_kill);
-void shake_folio(struct folio *folio);
typedef int hwpoison_filter_func_t(struct page *p);
void hwpoison_filter_register(hwpoison_filter_func_t *filter);
void hwpoison_filter_unregister(void);
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index aaf14608b30e..2a6a01e260ed 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -310,30 +310,6 @@ static int kill_proc(struct to_kill *tk, unsigned long pfn, int flags)
return ret;
}
-/*
- * Unknown page type encountered. Try to check whether it can turn PageLRU by
- * lru_add_drain_all.
- */
-void shake_folio(struct folio *folio)
-{
- if (folio_test_hugetlb(folio))
- return;
- /*
- * TODO: Could shrink slab caches here if a lightweight range-based
- * shrinker will be available.
- */
- if (folio_test_slab(folio))
- return;
-
- lru_add_drain_all();
-}
-EXPORT_SYMBOL_GPL(shake_folio);
-
-static void shake_page(struct page *page)
-{
- shake_folio(page_folio(page));
-}
-
static unsigned long dev_pagemap_mapping_shift(struct vm_area_struct *vma,
unsigned long address)
{
@@ -1459,10 +1435,8 @@ static int get_any_page(struct page *p, unsigned long flags)
* We raced with (possibly temporary) unhandlable
* page, retry.
*/
- if (pass++ < GET_PAGE_MAX_RETRY_NUM) {
- shake_page(p);
+ if (pass++ < GET_PAGE_MAX_RETRY_NUM)
goto try_again;
- }
ret = -EIO;
goto out;
}
@@ -1477,7 +1451,6 @@ static int get_any_page(struct page *p, unsigned long flags)
*/
if (pass++ < GET_PAGE_MAX_RETRY_NUM) {
put_page(p);
- shake_page(p);
count_increased = false;
goto try_again;
}
@@ -1627,7 +1600,6 @@ static bool hwpoison_user_mappings(struct folio *folio, struct page *p,
LIST_HEAD(tokill);
bool unmap_success;
bool forcekill;
- bool mlocked = folio_test_mlocked(folio);
/*
* Here we are interested only in user-mapped pages, so skip any
@@ -1658,13 +1630,6 @@ static bool hwpoison_user_mappings(struct folio *folio, struct page *p,
pr_err("%#lx: failed to unmap page (folio mapcount=%d)\n",
pfn, folio_mapcount(folio));
- /*
- * try_to_unmap() might put mlocked page in lru cache, so call
- * shake_page() again to ensure that it's flushed.
- */
- if (mlocked)
- shake_folio(folio);
-
/*
* Now that the dirty bit has been propagated to the
* struct page and all unmaps done we can decide if
@@ -2554,7 +2519,6 @@ int memory_failure(unsigned long pfn, int flags)
* The check (unnecessarily) ignores LRU pages being isolated and
* walked by the page reclaim code, however that's not a big loss.
*/
- shake_folio(folio);
folio_lock(folio);
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH 16/25] mm/fbatch: remove lru_cache_disable(() from NUMA folio migration
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
` (14 preceding siblings ...)
2026-08-24 14:27 ` [PATCH 15/25] mm/fbatch: remove shake_folio() shake_page() from memory-failure Hugh Dickins
@ 2026-08-24 14:30 ` Hugh Dickins
2026-08-24 14:32 ` [PATCH 17/25] mm/fbatch: no lru_cache_disable() in __alloc_contig_migrate_range() Hugh Dickins
` (8 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 14:30 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
Remove lru_cache_disable() from mbind(MOVE), migrate_pages and move_pages
syscall handling. They do not now benefit from lru_add_drain_all() first;
they gain little benefit from invalidating buffer head LRUs first, since
5.0 commit 80409c65e2c6 ("mm: migrate: make buffer_migrate_page_norefs()
actually succeed"); and it's a shame that (even without CAP_SYS_NICE)
they can prevent concurrent tasks from enjoying the use of those caches.
Signed-off-by: Hugh Dickins <hughd@google.com>
---
mm/mempolicy.c | 7 -------
mm/migrate.c | 3 ---
2 files changed, 10 deletions(-)
diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index 501e0b80d7da..44912d707d57 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -1343,8 +1343,6 @@ int do_migrate_pages(struct mm_struct *mm, const nodemask_t *from,
long err = 0;
nodemask_t tmp;
- lru_cache_disable();
-
/*
* Find a 'source' bit set in 'tmp' whose corresponding 'dest'
* bit in 'to' is not also set in 'tmp'. Clear the found 'source'
@@ -1425,7 +1423,6 @@ int do_migrate_pages(struct mm_struct *mm, const nodemask_t *from,
break;
}
- lru_cache_enable();
if (err < 0)
return err;
return (nr_failed < INT_MAX) ? nr_failed : INT_MAX;
@@ -1530,8 +1527,6 @@ static long do_mbind(unsigned long start, unsigned long len,
if (!new)
flags |= MPOL_MF_DISCONTIG_OK;
- if (flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL))
- lru_cache_disable();
{
NODEMASK_SCRATCH(scratch);
if (scratch) {
@@ -1626,8 +1621,6 @@ static long do_mbind(unsigned long start, unsigned long len,
putback_movable_pages(&pagelist);
mpol_out:
mpol_put(new);
- if (flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL))
- lru_cache_enable();
return err;
}
diff --git a/mm/migrate.c b/mm/migrate.c
index 534908a0839a..bf9fe533a5fb 100644
--- a/mm/migrate.c
+++ b/mm/migrate.c
@@ -2360,8 +2360,6 @@ static int do_pages_move(struct mm_struct *mm, nodemask_t task_nodes,
int start, i;
int err = 0, err1;
- lru_cache_disable();
-
for (i = start = 0; i < nr_pages; i++) {
const void __user *p;
int node;
@@ -2440,7 +2438,6 @@ static int do_pages_move(struct mm_struct *mm, nodemask_t task_nodes,
if (err >= 0)
err = err1;
out:
- lru_cache_enable();
return err;
}
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH 17/25] mm/fbatch: no lru_cache_disable() in __alloc_contig_migrate_range()
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
` (15 preceding siblings ...)
2026-08-24 14:30 ` [PATCH 16/25] mm/fbatch: remove lru_cache_disable(() from NUMA folio migration Hugh Dickins
@ 2026-08-24 14:32 ` Hugh Dickins
2026-08-24 14:34 ` [PATCH 18/25] mm/fbatch: remove lru_add_drain() and _all() calls from various Hugh Dickins
` (7 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 14:32 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
Remove lru_cache_disable() from __alloc_contig_migrate_range(). It does
not now benefit from lru_add_drain_all() first; and gains little benefit
from invalidating buffer head LRUs first, since 5.0 commit 80409c65e2c6
("mm: migrate: make buffer_migrate_page_norefs() actually succeed").
This will be more controversial. lru_cache_disable()+lru_cache_enable()
were brought in for CMA page migration, see 5.13 commit d479960e44f2
("mm: disable LRU pagevec during the migration temporarily") through
8cc621d2f45d ("mm: fs: invalidate BH LRU during page migration") - I
guess the testing there must have been on a 4.19-based Android kernel,
without 5.0's buffer_migrate_page_norefs().
It's possible that invalidating BH LRUs perhaps 0 times, perhaps N
times, will average out worse than invalidating 1 time and stopping
everyone else; or that folio_test_clear_lru() failures manifest more
than before (note how folio migration has retries on raised refcount,
but isolate_migratepages_block() no retry on failed test_clear_lru).
But let's give this a try and look out for regressions.
Don't delete lru_cache_disable() yet: leaving stale folio pointers in
the per-cpu fbatches, with folio_try_get() yet to come on them, would be
bad for memory hotremoval: the lru_cache_disable() in offline_pages()
protects from that.
Signed-off-by: Hugh Dickins <hughd@google.com>
---
mm/page_alloc.c | 3 ---
1 file changed, 3 deletions(-)
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 083cbcb5bdde..96ab51b78a5f 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -7135,8 +7135,6 @@ static int __alloc_contig_migrate_range(struct compact_control *cc,
.reason = MR_CONTIG_RANGE,
};
- lru_cache_disable();
-
while (pfn < end || !list_empty(&cc->migratepages)) {
if (fatal_signal_pending(current)) {
ret = -EINTR;
@@ -7170,7 +7168,6 @@ static int __alloc_contig_migrate_range(struct compact_control *cc,
break;
}
- lru_cache_enable();
if (ret < 0) {
if (!(cc->gfp_mask & __GFP_NOWARN) && ret == -EBUSY)
alloc_contig_dump_pages(&cc->migratepages);
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH 18/25] mm/fbatch: remove lru_add_drain() and _all() calls from various
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
` (16 preceding siblings ...)
2026-08-24 14:32 ` [PATCH 17/25] mm/fbatch: no lru_cache_disable() in __alloc_contig_migrate_range() Hugh Dickins
@ 2026-08-24 14:34 ` Hugh Dickins
2026-08-24 14:36 ` [PATCH 19/25] mm/fbatch: vm/stat_refresh include lru_add_drain() on each cpu Hugh Dickins
` (6 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 14:34 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
Splitting into little patches gets tedious: now that folios on per-cpu
fbatches no longer hold an extra reference (and have the lru flag set),
most calls to lru_add_drain(), and more importantly lru_add_drain_all(),
should be removed.
Remove lru_add_drain() from compact_zone().
Remove lru_add_drain_all() from compact_nodes() and compact_store().
Keep lru_add_drain_cpu_zone() in compact_zone(): it was always a bit of
a hack, a cheap way to get a local_lock() around pcp drain_pages_zone().
Remove lru_add_drain() and lru_add_drain_all() from generic_fadvise(
POSIX_FADV_DONTNEED), but still retry once on failure; remove outdated
comment line from mapping_try_invalidate().
Remove every lru_add_drain() and lru_add_drain_all() from khugepaged.c.
Remove lru_add_drain_all() from KSM's scan_get_next_rmap_item() restart.
Remove lru_add_drain() from wp_can_reuse_anon_folio() and do_swap_page().
Remove lru_add_drain(), lru_add_drain_all() from migrate_device_unmap().
Keep lru_add_drain()s in mm/gup.c populate_vma_page_range() and
faultin_page_range(): good housekeeping after a bulk operation.
Keep lru_add_drain()s in mm/madvise.c: the ones after a bulk op probably
do want to "Push any new pages onto the LRU now", and update the stats;
the ones before a bulk op may be trying to stabilize initial conditions,
or to minimize draining under ptlock.
Keep lru_add_drain_all() in memcg-v1's mem_cgroup_force_empty():
it probably does want to push onto the LRU and update the stats.
Keep lru_add_drain() after bulk op in mlock_vma_pages_range(), partly
for "Unevictable kB" accuracy; but remove the lru_add_drain() before it.
Keep lru_add_drain()s in swap_cluster_readahead(), swap_vma_readahead():
they do want to "Push any new pages onto the LRU now" (akpm 2.5.46).
Keep lru_add_drain()s and lru_add_drain_all() throughout mm/vmscan.c:
it works on LRUs, so it does need folios to be on an actual LRU.
Signed-off-by: Hugh Dickins <hughd@google.com>
---
mm/compaction.c | 12 +-----------
mm/fadvise.c | 17 +----------------
mm/khugepaged.c | 11 -----------
mm/ksm.c | 12 ------------
mm/memory.c | 14 ++------------
mm/migrate_device.c | 9 ---------
mm/mlock.c | 1 -
mm/truncate.c | 1 -
8 files changed, 4 insertions(+), 73 deletions(-)
diff --git a/mm/compaction.c b/mm/compaction.c
index 9e045a90ba21..8ed375b08f3e 100644
--- a/mm/compaction.c
+++ b/mm/compaction.c
@@ -2652,9 +2652,6 @@ compact_zone(struct compact_control *cc, struct capture_control *capc)
trace_mm_compaction_begin(cc, start_pfn, end_pfn, sync);
- /* lru_add_drain_all could be expensive with involving other CPUs */
- lru_add_drain();
-
while ((ret = compact_finished(cc)) == COMPACT_CONTINUE) {
int err;
unsigned long iteration_start_pfn = cc->migrate_pfn;
@@ -2969,9 +2966,6 @@ static int compact_nodes(void)
{
int ret, nid;
- /* Flush pending updates to the LRU lists */
- lru_add_drain_all();
-
for_each_online_node(nid) {
ret = compact_node(NODE_DATA(nid), false);
if (ret)
@@ -3036,12 +3030,8 @@ static ssize_t compact_store(struct device *dev,
{
int nid = dev->id;
- if (nid >= 0 && nid < nr_node_ids && node_online(nid)) {
- /* Flush pending updates to the LRU lists */
- lru_add_drain_all();
-
+ if (nid >= 0 && nid < nr_node_ids && node_online(nid))
compact_node(NODE_DATA(nid), false);
- }
return count;
}
diff --git a/mm/fadvise.c b/mm/fadvise.c
index b63fe21416ff..f788a5020384 100644
--- a/mm/fadvise.c
+++ b/mm/fadvise.c
@@ -143,27 +143,12 @@ int generic_fadvise(struct file *file, loff_t offset, loff_t len, int advice)
if (end_index >= start_index) {
unsigned long nr_failed = 0;
- /*
- * It's common to FADV_DONTNEED right after
- * the read or write that instantiates the
- * pages, in which case there will be some
- * sitting on the local LRU cache. Try to
- * avoid the expensive remote drain and the
- * second cache tree walk below by flushing
- * them out right away.
- */
- lru_add_drain();
-
mapping_try_invalidate(mapping, start_index, end_index,
&nr_failed);
-
/*
- * The failures may be due to the folio being
- * in the LRU cache of a remote CPU. Drain all
- * caches and try again.
+ * Retry if any failures, in case they were transient.
*/
if (nr_failed) {
- lru_add_drain_all();
invalidate_mapping_pages(mapping, start_index,
end_index);
}
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index b237f6e7662a..904a45c11fca 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -1225,10 +1225,6 @@ static enum scan_result __collapse_huge_page_swapin(struct mm_struct *mm,
if (pte)
pte_unmap(pte);
- /* Drain LRU cache to remove extra pin on the swapped in pages */
- if (swapped_in)
- lru_add_drain();
-
result = SCAN_SUCCEED;
out:
trace_mm_collapse_huge_page_swapin(mm, swapped_in, referenced, result,
@@ -2325,8 +2321,6 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
result = SCAN_FAIL;
goto xa_unlocked;
}
- /* drain lru cache to help folio_isolate_lru() */
- lru_add_drain();
} else if (folio_trylock(folio)) {
folio_get(folio);
xas_unlock_irq(&xas);
@@ -2340,8 +2334,6 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
page_cache_sync_readahead(mapping, &file->f_ra,
file, index,
end - index);
- /* drain lru cache to help folio_isolate_lru() */
- lru_add_drain();
folio = filemap_lock_folio(mapping, index);
if (IS_ERR(folio)) {
result = SCAN_FAIL;
@@ -2981,8 +2973,6 @@ static void khugepaged_do_scan(struct collapse_control *cc)
bool wait = true;
enum scan_result result = SCAN_SUCCEED;
- lru_add_drain_all();
-
cc->progress = 0;
while (true) {
cond_resched();
@@ -3214,7 +3204,6 @@ int madvise_collapse(struct vm_area_struct *vma, unsigned long start,
cc->progress = 0;
mmgrab(mm);
- lru_add_drain_all();
for (addr = hstart; addr < hend; addr += HPAGE_PMD_SIZE) {
enum scan_result result = SCAN_FAIL;
diff --git a/mm/ksm.c b/mm/ksm.c
index b4142746777e..5bae928620ad 100644
--- a/mm/ksm.c
+++ b/mm/ksm.c
@@ -2626,18 +2626,6 @@ static struct ksm_rmap_item *scan_get_next_rmap_item(struct page **page)
advisor_start_scan();
trace_ksm_start_scan(ksm_scan.seqnr, ksm_rmap_items);
- /*
- * A number of pages can hang around indefinitely in per-cpu
- * LRU cache, raised page count preventing write_protect_page
- * from merging them. Though it doesn't really matter much,
- * it is puzzling to see some stuck in pages_volatile until
- * other activity jostles them out, and they also prevented
- * LTP's KSM test from succeeding deterministically; so drain
- * them here (here rather than on entry to ksm_do_scan(),
- * so we don't IPI too often when pages_to_scan is set low).
- */
- lru_add_drain_all();
-
/*
* Whereas stale stable_nodes on the stable_tree itself
* get pruned in the regular course of stable_tree_search(),
diff --git a/mm/memory.c b/mm/memory.c
index 8da0f945141b..1478bee69e30 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -4251,9 +4251,6 @@ static bool __wp_can_reuse_large_anon_folio(struct folio *folio,
static bool wp_can_reuse_anon_folio(struct folio *folio,
struct vm_area_struct *vma)
{
- const bool maybe_in_lru_cache = !folio_test_lru(folio);
- const bool in_swapcache = folio_test_swapcache(folio);
-
if (IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE) && folio_test_large(folio))
return __wp_can_reuse_large_anon_folio(folio, vma);
@@ -4264,16 +4261,9 @@ static bool wp_can_reuse_anon_folio(struct folio *folio,
*
* KSM doesn't necessarily raise the folio refcount.
*/
- if (folio_test_ksm(folio) ||
- folio_ref_count(folio) > 1 + maybe_in_lru_cache + in_swapcache)
+ if (folio_test_ksm(folio))
return false;
- if (maybe_in_lru_cache)
- /*
- * We cannot easily detect+handle references from
- * remote LRU caches or references to LRU folios.
- */
- lru_add_drain();
- if (folio_ref_count(folio) > 1 + in_swapcache)
+ if (folio_ref_count(folio) > 1 + folio_test_swapcache(folio))
return false;
if (!folio_trylock(folio))
return false;
diff --git a/mm/migrate_device.c b/mm/migrate_device.c
index 18d097c38853..d3f865904c48 100644
--- a/mm/migrate_device.c
+++ b/mm/migrate_device.c
@@ -575,11 +575,8 @@ static unsigned long migrate_device_unmap(unsigned long *src_pfns,
struct folio *fault_folio = fault_page ?
page_folio(fault_page) : NULL;
unsigned long i, restore = 0;
- bool allow_drain = true;
unsigned long unmapped = 0;
- lru_add_drain();
-
for (i = 0; i < npages; ) {
struct page *page = migrate_pfn_to_page(src_pfns[i]);
struct folio *folio;
@@ -600,12 +597,6 @@ static unsigned long migrate_device_unmap(unsigned long *src_pfns,
/* ZONE_DEVICE folios are not on LRU */
if (!folio_is_zone_device(folio)) {
- if (!folio_test_lru(folio) && allow_drain) {
- /* Drain CPU's lru cache */
- lru_add_drain_all();
- allow_drain = false;
- }
-
if (!folio_isolate_lru(folio)) {
src_pfns[i] &= ~MIGRATE_PFN_MIGRATE;
restore++;
diff --git a/mm/mlock.c b/mm/mlock.c
index 97134eff6b56..971430e6251e 100644
--- a/mm/mlock.c
+++ b/mm/mlock.c
@@ -424,7 +424,6 @@ static void mlock_vma_pages_range(struct vm_area_struct *vma,
vma_start_write(vma);
vma_flags_reset_once(vma, new_vma_flags);
- lru_add_drain();
walk_page_range_vma(vma, start, end, &mlock_walk_ops, NULL);
lru_add_drain();
diff --git a/mm/truncate.c b/mm/truncate.c
index 4151f7a167e3..7ea5d513f76a 100644
--- a/mm/truncate.c
+++ b/mm/truncate.c
@@ -571,7 +571,6 @@ unsigned long mapping_try_invalidate(struct address_space *mapping,
*/
if (!ret) {
deactivate_file_folio(folio);
- /* Likely in the lru cache of a remote CPU */
if (nr_failed)
(*nr_failed)++;
}
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH 19/25] mm/fbatch: vm/stat_refresh include lru_add_drain() on each cpu
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
` (17 preceding siblings ...)
2026-08-24 14:34 ` [PATCH 18/25] mm/fbatch: remove lru_add_drain() and _all() calls from various Hugh Dickins
@ 2026-08-24 14:36 ` Hugh Dickins
2026-08-24 14:39 ` [PATCH 20/25] s390/fbatch: no lru_add_drain_all() in s390_wiggle_split_folio() Hugh Dickins
` (5 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 14:36 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
Reading or writing /proc/sys/vm/stat_refresh has long been a way for
testing to force an immediate refresh of /proc/vmstat numbers: it seems
appropriate that it should also call lru_add_drain() on each cpu that it
visits, before collecting that processor's diffs.
Add that in refresh_vm_stats(). I wanted it in refresh_cpu_vm_stats(),
which would include it in the regular (default HZ) vmstat_update() too:
which might be a wise precaution when proposing to eliminate almost all
the old calls to lru_add_drain_all(). However, that would go against its
"strives to only access node local memory" cacheline care, and bring it
into lruvec lock contention: which would probably displease its authors.
Signed-off-by: Hugh Dickins <hughd@google.com>
---
mm/vmstat.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/mm/vmstat.c b/mm/vmstat.c
index 3b5cb1031f72..0f75488687b5 100644
--- a/mm/vmstat.c
+++ b/mm/vmstat.c
@@ -1973,6 +1973,7 @@ static int vmstat_late_init_done;
#ifdef CONFIG_PROC_FS
static void refresh_vm_stats(struct work_struct *work)
{
+ lru_add_drain();
refresh_cpu_vm_stats(true);
}
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH 20/25] s390/fbatch: no lru_add_drain_all() in s390_wiggle_split_folio()
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
` (18 preceding siblings ...)
2026-08-24 14:36 ` [PATCH 19/25] mm/fbatch: vm/stat_refresh include lru_add_drain() on each cpu Hugh Dickins
@ 2026-08-24 14:39 ` Hugh Dickins
2026-08-24 14:41 ` [PATCH 21/25] block/fbatch: no lru_add_drain_all() in invalidate_bdev() Hugh Dickins
` (4 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 14:39 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
s390_wiggle_split_folio() has no good reason to lru_add_drain_all(),
now that the per-cpu fbatch references are gone.
Signed-off-by: Hugh Dickins <hughd@google.com>
---
arch/s390/kernel/uv.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/arch/s390/kernel/uv.c b/arch/s390/kernel/uv.c
index dc14ebc0105b..120a467026a5 100644
--- a/arch/s390/kernel/uv.c
+++ b/arch/s390/kernel/uv.c
@@ -364,7 +364,6 @@ int s390_wiggle_split_folio(struct mm_struct *mm, struct folio *folio)
lockdep_assert_not_held(&mm->mmap_lock);
folio_wait_writeback(folio);
- lru_add_drain_all();
if (!folio_test_large(folio))
return 0;
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH 21/25] block/fbatch: no lru_add_drain_all() in invalidate_bdev()
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
` (19 preceding siblings ...)
2026-08-24 14:39 ` [PATCH 20/25] s390/fbatch: no lru_add_drain_all() in s390_wiggle_split_folio() Hugh Dickins
@ 2026-08-24 14:41 ` Hugh Dickins
2026-08-24 14:44 ` [PATCH 22/25] fs/fbatch: drop_caches invalidate_bh_lrus() not lru_add_drain_all() Hugh Dickins
` (3 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 14:41 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
invalidate_bdev() has no good reason to lru_add_drain_all(), now that the
per-cpu fbatch references are gone; but keep doing invalidate_bh_lrus().
Signed-off-by: Hugh Dickins <hughd@google.com>
---
block/bdev.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/block/bdev.c b/block/bdev.c
index cd8323083740..853545b29b7f 100644
--- a/block/bdev.c
+++ b/block/bdev.c
@@ -97,7 +97,6 @@ void invalidate_bdev(struct block_device *bdev)
if (mapping->nrpages) {
invalidate_bh_lrus();
- lru_add_drain_all(); /* make sure all lru add caches are flushed */
invalidate_mapping_pages(mapping, 0, -1);
}
}
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH 22/25] fs/fbatch: drop_caches invalidate_bh_lrus() not lru_add_drain_all()
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
` (20 preceding siblings ...)
2026-08-24 14:41 ` [PATCH 21/25] block/fbatch: no lru_add_drain_all() in invalidate_bdev() Hugh Dickins
@ 2026-08-24 14:44 ` Hugh Dickins
2026-08-24 14:47 ` [PATCH 23/25] fs,mm/fbatch: use invalidate_bh_lrus() not invalidate_bh_lrus_cpu() Hugh Dickins
` (2 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 14:44 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
drop_caches has no good reason to lru_add_drain_all(), now that the
per-cpu fbatch references are gone; but it may have good reason to
do the invalidate_bh_lrus() part of it.
Signed-off-by: Hugh Dickins <hughd@google.com>
---
fs/drop_caches.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/fs/drop_caches.c b/fs/drop_caches.c
index 49f56a598ecb..054af78e6a2e 100644
--- a/fs/drop_caches.c
+++ b/fs/drop_caches.c
@@ -10,7 +10,7 @@
#include <linux/writeback.h>
#include <linux/sysctl.h>
#include <linux/gfp.h>
-#include <linux/swap.h>
+#include <linux/buffer_head.h>
#include "internal.h"
/* A global variable is a bit ugly, but it keeps the code simple */
@@ -60,7 +60,7 @@ static int drop_caches_sysctl_handler(const struct ctl_table *table, int write,
static int stfu;
if (sysctl_drop_caches & 1) {
- lru_add_drain_all();
+ invalidate_bh_lrus();
iterate_supers(drop_pagecache_sb, NULL);
count_vm_event(DROP_PAGECACHE);
}
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH 23/25] fs,mm/fbatch: use invalidate_bh_lrus() not invalidate_bh_lrus_cpu()
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
` (21 preceding siblings ...)
2026-08-24 14:44 ` [PATCH 22/25] fs/fbatch: drop_caches invalidate_bh_lrus() not lru_add_drain_all() Hugh Dickins
@ 2026-08-24 14:47 ` Hugh Dickins
2026-08-24 14:49 ` [PATCH 24/25] fs,mm/fbatch: lru_cache_disable() keep off buffer_head lrus only Hugh Dickins
2026-08-24 14:51 ` [PATCH 25/25] mm/fbatch: move lru_add_drain_all() declaration to mm/internal.h Hugh Dickins
24 siblings, 0 replies; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 14:47 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
Whilst there can be a case for including an invalidate_bh_lrus_cpu() in
lru_add_drain_all()'s workqueued visits to remote CPUs, wouldn't it now
be a preferable cleanup to remove that alternative, and stick with the
smp_call_function_many_cond()-based invalidate_bh_lrus() throughout?
And fix the UP lru_add_drain_all() to include an invalidate_bh_lrus(),
which went missing when 5.15 commit 243418e3925d ("mm: fs: invalidate
bh_lrus for only cold path") took that out of lru_add_drain().
Signed-off-by: Hugh Dickins <hughd@google.com>
---
fs/buffer.c | 16 +---------------
include/linux/buffer_head.h | 4 ----
mm/folio.c | 25 ++++++-------------------
3 files changed, 7 insertions(+), 38 deletions(-)
diff --git a/fs/buffer.c b/fs/buffer.c
index ed966fa73b1b..7d114e5b9c62 100644
--- a/fs/buffer.c
+++ b/fs/buffer.c
@@ -1427,7 +1427,7 @@ static void invalidate_bh_lru(void *arg)
put_cpu_var(bh_lrus);
}
-bool has_bh_in_lru(int cpu, void *dummy)
+static bool has_bh_in_lru(int cpu, void *dummy)
{
struct bh_lru *b = per_cpu_ptr(&bh_lrus, cpu);
int i;
@@ -1446,20 +1446,6 @@ void invalidate_bh_lrus(void)
}
EXPORT_SYMBOL_GPL(invalidate_bh_lrus);
-/*
- * It's called from workqueue context so we need a bh_lru_lock to close
- * the race with preemption/irq.
- */
-void invalidate_bh_lrus_cpu(void)
-{
- struct bh_lru *b;
-
- bh_lru_lock();
- b = this_cpu_ptr(&bh_lrus);
- __invalidate_bh_lrus(b);
- bh_lru_unlock();
-}
-
void folio_set_bh(struct buffer_head *bh, struct folio *folio,
unsigned long offset)
{
diff --git a/include/linux/buffer_head.h b/include/linux/buffer_head.h
index fd2c7115c054..f19f9e80be8f 100644
--- a/include/linux/buffer_head.h
+++ b/include/linux/buffer_head.h
@@ -518,8 +518,6 @@ bool mmb_has_buffers(struct mapping_metadata_bhs *mmb);
void mmb_invalidate(struct mapping_metadata_bhs *mmb);
int mmb_sync(struct mapping_metadata_bhs *mmb);
void invalidate_bh_lrus(void);
-void invalidate_bh_lrus_cpu(void);
-bool has_bh_in_lru(int cpu, void *dummy);
extern int buffer_heads_over_limit;
#else /* CONFIG_BUFFER_HEAD */
@@ -528,8 +526,6 @@ static inline void buffer_init(void) {}
static inline bool try_to_free_buffers(struct folio *folio) { return true; }
static inline int mmb_sync(struct mapping_metadata_bhs *mmb) { return 0; }
static inline void invalidate_bh_lrus(void) {}
-static inline void invalidate_bh_lrus_cpu(void) {}
-static inline bool has_bh_in_lru(int cpu, void *dummy) { return false; }
#define buffer_heads_over_limit 0
#endif /* CONFIG_BUFFER_HEAD */
diff --git a/mm/folio.c b/mm/folio.c
index 782b8245d213..3212c7a58623 100644
--- a/mm/folio.c
+++ b/mm/folio.c
@@ -748,21 +748,6 @@ void lru_add_drain(void)
mlock_drain_local();
}
-/*
- * It's called from per-cpu workqueue context in SMP case so
- * lru_add_drain_cpu and invalidate_bh_lrus_cpu should run on
- * the same cpu. It shouldn't be a problem in !SMP case since
- * the core is only one and the locks will disable preemption.
- */
-static void lru_add_and_bh_lrus_drain(void)
-{
- local_lock(&cpu_fbatches.lock);
- lru_add_drain_cpu(smp_processor_id());
- local_unlock(&cpu_fbatches.lock);
- invalidate_bh_lrus_cpu();
- mlock_drain_local();
-}
-
void lru_add_drain_cpu_zone(struct zone *zone)
{
local_lock(&cpu_fbatches.lock);
@@ -778,7 +763,7 @@ static DEFINE_PER_CPU(struct work_struct, lru_add_drain_work);
static void lru_add_drain_per_cpu(struct work_struct *dummy)
{
- lru_add_and_bh_lrus_drain();
+ lru_add_drain();
}
static bool cpu_needs_drain(unsigned int cpu)
@@ -791,8 +776,7 @@ static bool cpu_needs_drain(unsigned int cpu)
folio_batch_count(&fbatches->lru_move_tail) ||
folio_batch_count(&fbatches->lru_deactivate_file) ||
folio_batch_count(&fbatches->lru_deactivate) ||
- need_mlock_drain(cpu)) ||
- has_bh_in_lru(cpu, NULL);
+ need_mlock_drain(cpu));
}
/*
@@ -891,6 +875,8 @@ static inline void __lru_add_drain_all(bool force_all_cpus)
}
}
+ invalidate_bh_lrus();
+
for_each_cpu(cpu, &has_work)
flush_work(&per_cpu(lru_add_drain_work, cpu));
@@ -906,6 +892,7 @@ void lru_add_drain_all(void)
void lru_add_drain_all(void)
{
lru_add_drain();
+ invalidate_bh_lrus();
}
#endif /* CONFIG_SMP */
@@ -939,7 +926,7 @@ void lru_cache_disable(void)
#ifdef CONFIG_SMP
__lru_add_drain_all(true);
#else
- lru_add_and_bh_lrus_drain();
+ lru_add_drain_all();
#endif
}
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH 24/25] fs,mm/fbatch: lru_cache_disable() keep off buffer_head lrus only
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
` (22 preceding siblings ...)
2026-08-24 14:47 ` [PATCH 23/25] fs,mm/fbatch: use invalidate_bh_lrus() not invalidate_bh_lrus_cpu() Hugh Dickins
@ 2026-08-24 14:49 ` Hugh Dickins
2026-08-24 14:51 ` [PATCH 25/25] mm/fbatch: move lru_add_drain_all() declaration to mm/internal.h Hugh Dickins
24 siblings, 0 replies; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 14:49 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
Now that the per-cpu fbatch folio references are gone, there seems to
be no excuse for lru_cache_disable() there - other than offline_pages()
needing to lru_add_drain_all() to erase stale pointers from the fbatches.
Remove lru_cache_disabled() checks from all except bh_lru_install():
assuming that lru_cache_disable() might have value in preventing repeated
calls to invalidate_bh_lrus() when migrating folios in memory hotremoval.
So move all that from mm/folio.c to fs/buffer.c: but I can't see how any
of the paranoid synchronize_rcu_expedited() business is needed now (or
even before) - leave it out. And back at the mm end, lru_add_drain_all()
does not need that force_all_cpus either - it stopped forcing all cpus
in 5.18 commit ff042f4a9b05 ("mm: lru_cache_disable: replace work queue
synchronization with synchronize_rcu").
Signed-off-by: Hugh Dickins <hughd@google.com>
---
fs/buffer.c | 24 +++++++++++++++---
include/linux/buffer_head.h | 4 +++
include/linux/swap.h | 7 ------
mm/folio.c | 49 ++++---------------------------------
mm/internal.h | 6 -----
mm/memory_hotplug.c | 4 +++
mm/mlock.c | 4 +--
7 files changed, 36 insertions(+), 62 deletions(-)
diff --git a/fs/buffer.c b/fs/buffer.c
index 7d114e5b9c62..7455a11dfc4a 100644
--- a/fs/buffer.c
+++ b/fs/buffer.c
@@ -1200,6 +1200,24 @@ static inline void check_irqs_on(void)
#endif
}
+static atomic_t lru_disable_count = ATOMIC_INIT(0);
+
+void lru_cache_disable(void)
+{
+ if (atomic_inc_return(&lru_disable_count) == 1)
+ invalidate_bh_lrus();
+}
+
+static inline bool lru_cache_disabled(void)
+{
+ return atomic_read(&lru_disable_count);
+}
+
+void lru_cache_enable(void)
+{
+ atomic_dec(&lru_disable_count);
+}
+
/*
* Install a buffer_head into this cpu's LRU. If not already in the LRU, it is
* inserted at the front, and the buffer_head at the back if any is evicted.
@@ -1215,9 +1233,9 @@ static void bh_lru_install(struct buffer_head *bh)
bh_lru_lock();
/*
- * the refcount of buffer_head in bh_lru prevents dropping the
- * attached page(i.e., try_to_free_buffers) so it could cause
- * failing page migration.
+ * The refcount of buffer_head in bh_lru prevents dropping the
+ * attached page (i.e., try_to_free_buffers), so it could cause
+ * repeated calls to invalidate_bh_lrus() during page migration.
* Skip putting upcoming bh into bh_lru until migration is done.
*/
if (lru_cache_disabled() || cpu_is_isolated(smp_processor_id())) {
diff --git a/include/linux/buffer_head.h b/include/linux/buffer_head.h
index f19f9e80be8f..3b6a41b7932f 100644
--- a/include/linux/buffer_head.h
+++ b/include/linux/buffer_head.h
@@ -517,6 +517,8 @@ void mmb_init(struct mapping_metadata_bhs *mmb, struct address_space *mapping);
bool mmb_has_buffers(struct mapping_metadata_bhs *mmb);
void mmb_invalidate(struct mapping_metadata_bhs *mmb);
int mmb_sync(struct mapping_metadata_bhs *mmb);
+void lru_cache_disable(void);
+void lru_cache_enable(void);
void invalidate_bh_lrus(void);
extern int buffer_heads_over_limit;
@@ -525,6 +527,8 @@ extern int buffer_heads_over_limit;
static inline void buffer_init(void) {}
static inline bool try_to_free_buffers(struct folio *folio) { return true; }
static inline int mmb_sync(struct mapping_metadata_bhs *mmb) { return 0; }
+static inline void lru_cache_disable(void) {}
+static inline void lru_cache_enable(void) {}
static inline void invalidate_bh_lrus(void) {}
#define buffer_heads_over_limit 0
diff --git a/include/linux/swap.h b/include/linux/swap.h
index f21e1dd6febc..ddfd9b7bb861 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -305,13 +305,6 @@ void lru_add_drain_all(void);
/* linux/mm/folio-compat.c */
void mark_page_accessed(struct page *page);
-extern atomic_t lru_disable_count;
-
-static inline bool lru_cache_disabled(void)
-{
- return atomic_read(&lru_disable_count);
-}
-
extern unsigned long shrink_all_memory(unsigned long nr_pages);
extern int vm_swappiness;
long remove_mapping(struct address_space *mapping, struct folio *folio);
diff --git a/mm/folio.c b/mm/folio.c
index 3212c7a58623..dac2f2d5dcc1 100644
--- a/mm/folio.c
+++ b/mm/folio.c
@@ -173,7 +173,7 @@ static void __folio_batch_add_and_move(struct folio_batch __percpu *fbatch,
local_lock(&cpu_fbatches.lock);
if (!folio_batch_add(this_cpu_ptr(fbatch), folio) ||
- !folio_may_be_lru_cached(folio) || lru_cache_disabled())
+ !folio_may_be_lru_cached(folio))
folio_batch_move_lru(this_cpu_ptr(fbatch), move_fn);
if (disable_irq)
@@ -501,7 +501,7 @@ void __folio_add_lru(struct folio *folio, bool mlockit)
smp_mb__before_atomic();
folio_set_lru(folio);
- if (full || !folio_may_be_lru_cached(folio) || lru_cache_disabled())
+ if (full || !folio_may_be_lru_cached(folio))
folio_batch_move_lru(fbatch, lru_add);
local_unlock(&cpu_fbatches.lock);
@@ -786,7 +786,7 @@ static bool cpu_needs_drain(unsigned int cpu)
* Calling this function with cpu hotplug locks held can actually lead
* to obscure indirect dependencies via WQ context.
*/
-static inline void __lru_add_drain_all(bool force_all_cpus)
+void lru_add_drain_all(void)
{
/*
* lru_drain_gen - Global pages generation number
@@ -810,7 +810,7 @@ static inline void __lru_add_drain_all(bool force_all_cpus)
if (WARN_ON(!mm_percpu_wq))
return;
- trace_mm_lru_add_drain_all_tp(force_all_cpus);
+ trace_mm_lru_add_drain_all_tp(false);
/*
* Guarantee folio_batch counter stores visible by this CPU
@@ -837,7 +837,7 @@ static inline void __lru_add_drain_all(bool force_all_cpus)
* (C) Exit the draining operation if a newer generation, from another
* lru_add_drain_all(), was already scheduled for draining. Check (A).
*/
- if (unlikely(this_gen != lru_drain_gen && !force_all_cpus))
+ if (unlikely(this_gen != lru_drain_gen))
goto done;
/*
@@ -883,11 +883,6 @@ static inline void __lru_add_drain_all(bool force_all_cpus)
done:
mutex_unlock(&lock);
}
-
-void lru_add_drain_all(void)
-{
- __lru_add_drain_all(false);
-}
#else
void lru_add_drain_all(void)
{
@@ -896,40 +891,6 @@ void lru_add_drain_all(void)
}
#endif /* CONFIG_SMP */
-atomic_t lru_disable_count = ATOMIC_INIT(0);
-
-/*
- * lru_cache_disable() needs to be called before we start compiling
- * a list of folios to be migrated using folio_isolate_lru().
- * It drains folios on LRU cache and then disable on all cpus until
- * lru_cache_enable is called.
- *
- * Must be paired with a call to lru_cache_enable().
- */
-void lru_cache_disable(void)
-{
- atomic_inc(&lru_disable_count);
- /*
- * Readers of lru_disable_count are protected by either disabling
- * preemption or rcu_read_lock:
- *
- * preempt_disable, local_irq_disable [bh_lru_lock()]
- * rcu_read_lock [rt_spin_lock CONFIG_PREEMPT_RT]
- * preempt_disable [local_lock !CONFIG_PREEMPT_RT]
- *
- * Since v5.1 kernel, synchronize_rcu() is guaranteed to wait on
- * preempt_disable() regions of code. So any CPU which sees
- * lru_disable_count = 0 will have exited the critical
- * section when synchronize_rcu() returns.
- */
- synchronize_rcu_expedited();
-#ifdef CONFIG_SMP
- __lru_add_drain_all(true);
-#else
- lru_add_drain_all();
-#endif
-}
-
/**
* folios_put_refs - Reduce the reference count on a batch of folios.
* @folios: The folios.
diff --git a/mm/internal.h b/mm/internal.h
index 9a25552cbd83..7a301c401d39 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -56,12 +56,6 @@ static inline bool folio_may_be_lru_cached(struct folio *folio)
return !folio_test_large(folio);
}
-static inline void lru_cache_enable(void)
-{
- atomic_dec(&lru_disable_count);
-}
-
-void lru_cache_disable(void);
void lru_add_drain(void);
void lru_add_drain_cpu(int cpu);
void lru_add_drain_cpu_zone(struct zone *zone);
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index 226ab9cb078a..19756c45b5e7 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -24,6 +24,7 @@
#include <linux/ioport.h>
#include <linux/delay.h>
#include <linux/migrate.h>
+#include <linux/buffer_head.h>
#include <linux/page-isolation.h>
#include <linux/pfn.h>
#include <linux/suspend.h>
@@ -2095,6 +2096,9 @@ int offline_pages(unsigned long start_pfn, unsigned long nr_pages,
} while (ret);
+ /* Remove any instances of the freed pages from per-cpu fbatches. */
+ lru_add_drain_all();
+
/* Mark all sections offline and remove free pages from the buddy. */
managed_pages = __offline_isolated_pages(start_pfn, end_pfn);
pr_debug("Offlined Pages %ld\n", nr_pages);
diff --git a/mm/mlock.c b/mm/mlock.c
index 971430e6251e..a3cfdb274fc7 100644
--- a/mm/mlock.c
+++ b/mm/mlock.c
@@ -247,7 +247,7 @@ void mlock_folio(struct folio *folio)
local_lock(&mlock_fbatch.lock);
fbatch = this_cpu_ptr(&mlock_fbatch.fbatch);
if (!folio_batch_add(fbatch, mlock_flagged(folio)) ||
- !folio_may_be_lru_cached(folio) || lru_cache_disabled())
+ !folio_may_be_lru_cached(folio))
mlock_folio_batch(fbatch);
local_unlock(&mlock_fbatch.lock);
}
@@ -278,7 +278,7 @@ void munlock_folio(struct folio *folio)
local_lock(&mlock_fbatch.lock);
fbatch = this_cpu_ptr(&mlock_fbatch.fbatch);
if (!folio_batch_add(fbatch, folio) ||
- !folio_may_be_lru_cached(folio) || lru_cache_disabled())
+ !folio_may_be_lru_cached(folio))
mlock_folio_batch(fbatch);
local_unlock(&mlock_fbatch.lock);
}
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH 25/25] mm/fbatch: move lru_add_drain_all() declaration to mm/internal.h
2026-08-24 13:49 [PATCH 00/25] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
` (23 preceding siblings ...)
2026-08-24 14:49 ` [PATCH 24/25] fs,mm/fbatch: lru_cache_disable() keep off buffer_head lrus only Hugh Dickins
@ 2026-08-24 14:51 ` Hugh Dickins
24 siblings, 0 replies; 28+ messages in thread
From: Hugh Dickins @ 2026-08-24 14:51 UTC (permalink / raw)
To: Andrew Morton
Cc: Ackerley Tng, Alexander Viro, Baolin Wang, Barry Song, Binbin Wu,
Christian Brauner, Christoph Hellwig, Christoph Lameter,
Claudio Imbrenda, David Hildenbrand, JP Kobryn, Jan Kara,
Jens Axboe, Johannes Weiner, Kairui Song, Kiryl Shutsemau,
Lance Yang, Leonardo Bras, Lorenzo Stoakes, Marcelo Tosatti,
Matthew Wilcox, Mel Gorman, Miaohe Lin, Michal Hocko,
Minchan Kim, Muchun Song, Oscar Salvador, Peter Zijlstra,
Qi Zheng, Rik van Riel, Sebastian Andrzej Siewior, Shakeel Butt,
Suren Baghdasaryan, Vlastimil Babka, Yang Shi, Yu Zhao,
Zach O'Keefe, Zi Yan, linux-block, linux-fsdevel,
linux-kernel, linux-mm
Only mm now uses lru_add_drain_all(), in forced reclaim and in memory
hotremoval: help to keep it that way by moving its declaration alongside
that of lru_add_drain() in mm/internal.h.
Signed-off-by: Hugh Dickins <hughd@google.com>
---
include/linux/swap.h | 1 -
mm/internal.h | 1 +
2 files changed, 1 insertion(+), 1 deletion(-)
diff --git a/include/linux/swap.h b/include/linux/swap.h
index ddfd9b7bb861..94b694328ee7 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -300,7 +300,6 @@ static inline void folio_add_lru(struct folio *folio)
__folio_add_lru(folio, false);
}
void folio_mark_accessed(struct folio *folio);
-void lru_add_drain_all(void);
/* linux/mm/folio-compat.c */
void mark_page_accessed(struct page *page);
diff --git a/mm/internal.h b/mm/internal.h
index 7a301c401d39..73d618254a3b 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -57,6 +57,7 @@ static inline bool folio_may_be_lru_cached(struct folio *folio)
}
void lru_add_drain(void);
+void lru_add_drain_all(void);
void lru_add_drain_cpu(int cpu);
void lru_add_drain_cpu_zone(struct zone *zone);
void folio_deactivate(struct folio *folio);
--
2.51.0
^ permalink raw reply [flat|nested] 28+ messages in thread