* [PATCH v7 1/4] mm: add page_counter_margin()
2026-08-30 4:29 [PATCH v7 0/4] mm: avoid large folio splits when swap is unavailable Xueyuan Chen
@ 2026-08-30 4:29 ` Xueyuan Chen
2026-08-30 4:42 ` Barry Song
2026-08-30 4:29 ` [PATCH v7 2/4] mm: distinguish large folio swap allocation failures Xueyuan Chen
` (3 subsequent siblings)
4 siblings, 1 reply; 8+ messages in thread
From: Xueyuan Chen @ 2026-08-30 4:29 UTC (permalink / raw)
To: akpm, linux-mm
Cc: linux-kernel, cgroups, zhaonanzhe, baohua, hannes, ryncsn,
youngjun.park, baolin.wang, hughd, chrisl, shikemeng, nphamcs,
baoquan.he, mhocko, roman.gushchin, shakeel.butt, muchun.song,
david, ljs, xueyuan.chen21
From: Johannes Weiner <hannes@cmpxchg.org>
mem_cgroup_get_nr_swap_pages() open-codes the remaining capacity across
the memcg swap counter hierarchy.
Add page_counter_margin() to return the minimum usable space from a page
counter to the root, and use it in mem_cgroup_get_nr_swap_pages(). This
is a pure refactoring with no intended behavior change.
Signed-off-by: Johannes Weiner <hannes@cmpxchg.org>
Signed-off-by: Xueyuan Chen <xueyuan.chen21@gmail.com>
Reviewed-by: David Hildenbrand (Arm) <david@kernel.org>
---
include/linux/page_counter.h | 1 +
mm/memcontrol.c | 9 +++------
mm/page_counter.c | 20 ++++++++++++++++++++
3 files changed, 24 insertions(+), 6 deletions(-)
diff --git a/include/linux/page_counter.h b/include/linux/page_counter.h
index d649b6bbbc87..07b7cb12249c 100644
--- a/include/linux/page_counter.h
+++ b/include/linux/page_counter.h
@@ -68,6 +68,7 @@ static inline unsigned long page_counter_read(struct page_counter *counter)
return atomic_long_read(&counter->usage);
}
+long page_counter_margin(struct page_counter *counter);
void page_counter_cancel(struct page_counter *counter, unsigned long nr_pages);
void page_counter_charge(struct page_counter *counter, unsigned long nr_pages);
bool page_counter_try_charge(struct page_counter *counter,
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index 8319ad8c5c23..109c08be91cf 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -5670,12 +5670,9 @@ long mem_cgroup_get_nr_swap_pages(struct mem_cgroup *memcg)
{
long nr_swap_pages = get_nr_swap_pages();
- if (mem_cgroup_disabled() || do_memsw_account())
- return nr_swap_pages;
- for (; !mem_cgroup_is_root(memcg); memcg = parent_mem_cgroup(memcg))
- nr_swap_pages = min_t(long, nr_swap_pages,
- READ_ONCE(memcg->swap.max) -
- page_counter_read(&memcg->swap));
+ if (!mem_cgroup_disabled() && !do_memsw_account())
+ nr_swap_pages = min(nr_swap_pages, page_counter_margin(&memcg->swap));
+
return nr_swap_pages;
}
diff --git a/mm/page_counter.c b/mm/page_counter.c
index 661e0f2a5127..450543f4b318 100644
--- a/mm/page_counter.c
+++ b/mm/page_counter.c
@@ -46,6 +46,26 @@ static void propagate_protected_usage(struct page_counter *c,
}
}
+/**
+ * page_counter_margin - remaining usable space within hierarchical limits
+ * @counter: counter
+ *
+ * Return: The minimum value of max minus usage across @counter and all of
+ * its ancestors. The value may be negative during a concurrent charge.
+ */
+long page_counter_margin(struct page_counter *counter)
+{
+ long margin = PAGE_COUNTER_MAX;
+
+ do {
+ long m = READ_ONCE(counter->max) - page_counter_read(counter);
+
+ margin = min(margin, m);
+ } while ((counter = counter->parent));
+
+ return margin;
+}
+
/**
* page_counter_cancel - take pages out of the local counter
* @counter: counter
base-commit: c73b725a57f276a3702ca213bde78fca029bc619
--
2.47.3
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH v7 1/4] mm: add page_counter_margin()
2026-08-30 4:29 ` [PATCH v7 1/4] mm: add page_counter_margin() Xueyuan Chen
@ 2026-08-30 4:42 ` Barry Song
0 siblings, 0 replies; 8+ messages in thread
From: Barry Song @ 2026-08-30 4:42 UTC (permalink / raw)
To: Xueyuan Chen
Cc: akpm, linux-mm, linux-kernel, cgroups, zhaonanzhe, hannes, ryncsn,
youngjun.park, baolin.wang, hughd, chrisl, shikemeng, nphamcs,
baoquan.he, mhocko, roman.gushchin, shakeel.butt, muchun.song,
david, ljs
On Sun, Aug 30, 2026 at 12:29 PM Xueyuan Chen <xueyuan.chen21@gmail.com> wrote:
>
> From: Johannes Weiner <hannes@cmpxchg.org>
>
> mem_cgroup_get_nr_swap_pages() open-codes the remaining capacity across
> the memcg swap counter hierarchy.
>
> Add page_counter_margin() to return the minimum usable space from a page
> counter to the root, and use it in mem_cgroup_get_nr_swap_pages(). This
> is a pure refactoring with no intended behavior change.
>
> Signed-off-by: Johannes Weiner <hannes@cmpxchg.org>
> Signed-off-by: Xueyuan Chen <xueyuan.chen21@gmail.com>
> Reviewed-by: David Hildenbrand (Arm) <david@kernel.org>
> ---
Thanks for working over the weekend. I've been waiting for this
resending for a while. :-). LGTM,
Reviewed-by: Barry Song <baohua@kernel.org>
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v7 2/4] mm: distinguish large folio swap allocation failures
2026-08-30 4:29 [PATCH v7 0/4] mm: avoid large folio splits when swap is unavailable Xueyuan Chen
2026-08-30 4:29 ` [PATCH v7 1/4] mm: add page_counter_margin() Xueyuan Chen
@ 2026-08-30 4:29 ` Xueyuan Chen
2026-08-30 4:29 ` [PATCH v7 3/4] mm/vmscan: avoid pointless large folio splits without swap Xueyuan Chen
` (2 subsequent siblings)
4 siblings, 0 replies; 8+ messages in thread
From: Xueyuan Chen @ 2026-08-30 4:29 UTC (permalink / raw)
To: akpm, linux-mm
Cc: linux-kernel, cgroups, zhaonanzhe, baohua, hannes, ryncsn,
youngjun.park, baolin.wang, hughd, chrisl, shikemeng, nphamcs,
baoquan.he, mhocko, roman.gushchin, shakeel.butt, muchun.song,
david, ljs, xueyuan.chen21
folio_alloc_swap() reports most failures with generic negative error
codes. Reclaim callers consequently cannot tell whether splitting a large
folio could make progress, or whether no swap space is available for even
a single page.
Classify failures using both the global free swap count and the remaining
capacity in the folio's memcg swap hierarchy. Return -ENOSPC when global
swap space is exhausted, -ENOMEM when splitting cannot overcome the
failure, and -E2BIG for a large folio when allocating or charging a
smaller folio might still succeed.
Use this classification for all folio_alloc_swap() failure paths,
including capability rejection, swap slot allocation failure, and memcg
swap charge failure. Callers are updated separately to split large folios
only on -E2BIG.
Suggested-by: Kairui Song <ryncsn@gmail.com>
Suggested-by: Barry Song <baohua@kernel.org>
Suggested-by: Youngjun Park <youngjun.park@lge.com>
Signed-off-by: Xueyuan Chen <xueyuan.chen21@gmail.com>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
---
include/linux/swap.h | 6 ++++++
mm/memcontrol.c | 23 +++++++++++++++++++++++
mm/swapfile.c | 26 +++++++++++++++++++-------
3 files changed, 48 insertions(+), 7 deletions(-)
diff --git a/include/linux/swap.h b/include/linux/swap.h
index 0544b2ec4c56..b23108d992aa 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -525,6 +525,7 @@ static inline void mem_cgroup_uncharge_swap(unsigned short id, unsigned int nr_p
__mem_cgroup_uncharge_swap(id, nr_pages);
}
+long mem_cgroup_get_folio_swap_margin(struct folio *folio);
extern long mem_cgroup_get_nr_swap_pages(struct mem_cgroup *memcg);
extern bool mem_cgroup_swap_full(struct folio *folio);
#else
@@ -538,6 +539,11 @@ static inline void mem_cgroup_uncharge_swap(unsigned short id,
{
}
+static inline long mem_cgroup_get_folio_swap_margin(struct folio *folio)
+{
+ return PAGE_COUNTER_MAX;
+}
+
static inline long mem_cgroup_get_nr_swap_pages(struct mem_cgroup *memcg)
{
return get_nr_swap_pages();
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index 109c08be91cf..4b42f3fc6075 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -5676,6 +5676,29 @@ long mem_cgroup_get_nr_swap_pages(struct mem_cgroup *memcg)
return nr_swap_pages;
}
+/**
+ * mem_cgroup_get_folio_swap_margin - get a folio's memcg swap margin
+ * @folio: folio whose memcg margin is queried
+ *
+ * Return: Remaining chargeable pages in the folio's memcg hierarchy.
+ */
+long mem_cgroup_get_folio_swap_margin(struct folio *folio)
+{
+ struct mem_cgroup *memcg;
+ long margin;
+
+ if (mem_cgroup_disabled() || do_memsw_account() ||
+ !folio_memcg_charged(folio))
+ return PAGE_COUNTER_MAX;
+
+ rcu_read_lock();
+ memcg = folio_memcg(folio);
+ margin = page_counter_margin(&memcg->swap);
+ rcu_read_unlock();
+
+ return margin;
+}
+
bool mem_cgroup_swap_full(struct folio *folio)
{
struct mem_cgroup *memcg;
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 70b90fa9c2a0..651682b1fe63 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -1735,7 +1735,9 @@ static int swap_dup_entries_cluster(struct swap_info_struct *si,
* swap cache.
*
* Context: Caller needs to hold the folio lock.
- * Return: Whether the folio was added to the swap cache.
+ * Return: %0 on success, %-E2BIG if splitting the folio might allow swapout,
+ * %-ENOSPC if no global swap space is available, or %-ENOMEM if splitting
+ * would not help.
*/
int folio_alloc_swap(struct folio *folio)
{
@@ -1747,11 +1749,11 @@ int folio_alloc_swap(struct folio *folio)
if (order) {
/*
- * Reject large allocation when THP_SWAP is disabled,
- * the caller should split the folio and try again.
+ * Reject large allocation when THP_SWAP is disabled. Check below
+ * whether splitting and retrying can make progress.
*/
if (!IS_ENABLED(CONFIG_THP_SWAP))
- return -EAGAIN;
+ goto failed;
/*
* Allocation size should never exceed cluster size
@@ -1759,7 +1761,7 @@ int folio_alloc_swap(struct folio *folio)
*/
if (size > SWAPFILE_CLUSTER) {
VM_WARN_ON_ONCE(1);
- return -EINVAL;
+ goto failed;
}
}
@@ -1775,13 +1777,23 @@ int folio_alloc_swap(struct folio *folio)
}
/* Need to call this even if allocation failed, for MEMCG_SWAP_FAIL. */
- if (unlikely(mem_cgroup_try_charge_swap(folio)))
+ if (unlikely(mem_cgroup_try_charge_swap(folio))) {
swap_cache_del_folio(folio);
+ goto failed;
+ }
if (unlikely(!folio_test_swapcache(folio)))
- return -ENOMEM;
+ goto failed;
return 0;
+
+failed:
+ if (get_nr_swap_pages() <= 0)
+ return -ENOSPC;
+ if (mem_cgroup_get_folio_swap_margin(folio) <= 0)
+ return -ENOMEM;
+
+ return order ? -E2BIG : -ENOMEM;
}
/**
--
2.47.3
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH v7 3/4] mm/vmscan: avoid pointless large folio splits without swap
2026-08-30 4:29 [PATCH v7 0/4] mm: avoid large folio splits when swap is unavailable Xueyuan Chen
2026-08-30 4:29 ` [PATCH v7 1/4] mm: add page_counter_margin() Xueyuan Chen
2026-08-30 4:29 ` [PATCH v7 2/4] mm: distinguish large folio swap allocation failures Xueyuan Chen
@ 2026-08-30 4:29 ` Xueyuan Chen
2026-08-30 4:29 ` [PATCH v7 4/4] mm/shmem: split large folios only on -E2BIG Xueyuan Chen
2026-09-01 3:39 ` [PATCH v7 0/4] mm: avoid large folio splits when swap is unavailable Andrew Morton
4 siblings, 0 replies; 8+ messages in thread
From: Xueyuan Chen @ 2026-08-30 4:29 UTC (permalink / raw)
To: akpm, linux-mm
Cc: linux-kernel, cgroups, zhaonanzhe, baohua, hannes, ryncsn,
youngjun.park, baolin.wang, hughd, chrisl, shikemeng, nphamcs,
baoquan.he, mhocko, roman.gushchin, shakeel.butt, muchun.song,
david, ljs, xueyuan.chen21
From: "Barry Song (Xiaomi)" <baohua@kernel.org>
When swap is disabled, exhausted, or unavailable due to memcg swap
limits, splitting a large anonymous folio cannot make swapout progress.
The fallback only destroys the large folio and inflates split statistics.
Use -E2BIG from folio_alloc_swap() as the explicit signal that splitting
the folio might allow swapout of smaller pieces. For other allocation
failures, keep the existing activation path and avoid the split.
This preserves the split fallback for fragmented or partially available
swap, while avoiding it when there is no backing space for any part of the
folio.
Reported-by: Nanzhe Zhao <zhaonanzhe@xiaomi.com>
Signed-off-by: Barry Song (Xiaomi) <baohua@kernel.org>
Signed-off-by: Xueyuan Chen <xueyuan.chen21@gmail.com>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
Reviewed-by: Baolin Wang <baolin.wang@linux.alibaba.com>
---
mm/vmscan.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 26df81e773ff..457049e130df 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -1263,6 +1263,8 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,
*/
if (folio_test_anon(folio) && folio_test_swapbacked(folio) &&
!folio_test_swapcache(folio)) {
+ int ret;
+
if (!(sc->gfp_mask & __GFP_IO))
goto keep_locked;
if (folio_maybe_dma_pinned(folio))
@@ -1281,11 +1283,14 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,
split_folio_to_list(folio, folio_list))
goto activate_locked;
}
- if (folio_alloc_swap(folio)) {
+ ret = folio_alloc_swap(folio);
+ if (ret) {
int __maybe_unused order = folio_order(folio);
if (!folio_test_large(folio))
goto activate_locked_split;
+ if (ret != -E2BIG)
+ goto activate_locked;
/* Fallback to swap normal pages */
if (split_folio_to_list(folio, folio_list))
goto activate_locked;
--
2.47.3
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH v7 4/4] mm/shmem: split large folios only on -E2BIG
2026-08-30 4:29 [PATCH v7 0/4] mm: avoid large folio splits when swap is unavailable Xueyuan Chen
` (2 preceding siblings ...)
2026-08-30 4:29 ` [PATCH v7 3/4] mm/vmscan: avoid pointless large folio splits without swap Xueyuan Chen
@ 2026-08-30 4:29 ` Xueyuan Chen
2026-09-01 3:39 ` [PATCH v7 0/4] mm: avoid large folio splits when swap is unavailable Andrew Morton
4 siblings, 0 replies; 8+ messages in thread
From: Xueyuan Chen @ 2026-08-30 4:29 UTC (permalink / raw)
To: akpm, linux-mm
Cc: linux-kernel, cgroups, zhaonanzhe, baohua, hannes, ryncsn,
youngjun.park, baolin.wang, hughd, chrisl, shikemeng, nphamcs,
baoquan.he, mhocko, roman.gushchin, shakeel.butt, muchun.song,
david, ljs, xueyuan.chen21
shmem_writeout() currently splits a large folio on every
folio_alloc_swap() failure. With the refined return-value contract, only
-E2BIG indicates that splitting might allow smaller folios to be swapped
out.
Enter the split fallback only for -E2BIG. For -ENOSPC and -ENOMEM,
redirty and reactivate the folio as before.
Suggested-by: Baolin Wang <baolin.wang@linux.alibaba.com>
Reviewed-by: Baolin Wang <baolin.wang@linux.alibaba.com>
Signed-off-by: Xueyuan Chen <xueyuan.chen21@gmail.com>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
Reviewed-by: Barry Song <baohua@kernel.org>
---
mm/shmem.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/mm/shmem.c b/mm/shmem.c
index 6641823bed16..eff38bb33d8a 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -1608,7 +1608,7 @@ int shmem_writeout(struct swap_io_ctx *ctx, struct folio *folio,
struct shmem_inode_info *info = SHMEM_I(inode);
struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb);
pgoff_t index;
- int nr_pages;
+ int nr_pages, ret;
bool split = false;
if ((info->flags & SHMEM_F_LOCKED) || sbinfo->noswap)
@@ -1689,7 +1689,8 @@ int shmem_writeout(struct swap_io_ctx *ctx, struct folio *folio,
folio_mark_uptodate(folio);
}
- if (!folio_alloc_swap(folio)) {
+ ret = folio_alloc_swap(folio);
+ if (!ret) {
bool first_swapped = shmem_recalc_inode(inode, 0, nr_pages);
int error;
@@ -1742,7 +1743,7 @@ int shmem_writeout(struct swap_io_ctx *ctx, struct folio *folio,
swap_cache_del_folio(folio);
goto redirty;
}
- if (nr_pages > 1)
+ if (nr_pages > 1 && ret == -E2BIG)
goto try_split;
redirty:
folio_mark_dirty(folio);
--
2.47.3
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH v7 0/4] mm: avoid large folio splits when swap is unavailable
2026-08-30 4:29 [PATCH v7 0/4] mm: avoid large folio splits when swap is unavailable Xueyuan Chen
` (3 preceding siblings ...)
2026-08-30 4:29 ` [PATCH v7 4/4] mm/shmem: split large folios only on -E2BIG Xueyuan Chen
@ 2026-09-01 3:39 ` Andrew Morton
2026-09-01 8:01 ` Xueyuan Chen
4 siblings, 1 reply; 8+ messages in thread
From: Andrew Morton @ 2026-09-01 3:39 UTC (permalink / raw)
To: Xueyuan Chen
Cc: linux-mm, linux-kernel, cgroups, zhaonanzhe, baohua, hannes,
ryncsn, youngjun.park, baolin.wang, hughd, chrisl, shikemeng,
nphamcs, baoquan.he, mhocko, roman.gushchin, shakeel.butt,
muchun.song, david, ljs
On Sun, 30 Aug 2026 12:29:16 +0800 Xueyuan Chen <xueyuan.chen21@gmail.com> wrote:
> This is v7 of Barry's original RFC patch, "mm: Avoiding split large
> folios if swap has no space":
>
> https://lore.kernel.org/r/20260618221720.71768-1-baohua@kernel.org
>
> Barry's RFC showed the no-swap case with MADV_PAGEOUT on 16KB mTHP: the
> large-folio split counter increased by 1024 even though no swapout
> progress was possible. Skipping the split in that case kept the counter
> at 0.
>
> This series makes folio_alloc_swap() classify failures according to
> whether splitting a large folio might allow swapout to make progress.
> Callers can then avoid destroying the large folio when neither global
> swap availability nor the folio's memcg swap hierarchy has capacity for
> even a smaller folio.
Thanks. Sashiko had a question which didn't occur against v6:
https://sashiko.dev/#/patchset/20260830-folio_swap_entry-v1-0-7786b52acbc8@columbia.edu
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH v7 0/4] mm: avoid large folio splits when swap is unavailable
2026-09-01 3:39 ` [PATCH v7 0/4] mm: avoid large folio splits when swap is unavailable Andrew Morton
@ 2026-09-01 8:01 ` Xueyuan Chen
0 siblings, 0 replies; 8+ messages in thread
From: Xueyuan Chen @ 2026-09-01 8:01 UTC (permalink / raw)
To: Andrew Morton
Cc: linux-mm, linux-kernel, cgroups, zhaonanzhe, baohua, hannes,
ryncsn, youngjun.park, baolin.wang, hughd, chrisl, shikemeng,
nphamcs, baoquan.he, mhocko, roman.gushchin, shakeel.butt,
muchun.song, david, ljs
[-- Attachment #1: Type: text/plain, Size: 1434 bytes --]
On Tue, Sep 1, 2026 at 11:39 AM Andrew Morton <akpm@linux-foundation.org>
wrote:
> On Sun, 30 Aug 2026 12:29:16 +0800 Xueyuan Chen <xueyuan.chen21@gmail.com>
> wrote:
>
> > This is v7 of Barry's original RFC patch, "mm: Avoiding split large
> > folios if swap has no space":
> >
> > https://lore.kernel.org/r/20260618221720.71768-1-baohua@kernel.org
> >
> > Barry's RFC showed the no-swap case with MADV_PAGEOUT on 16KB mTHP: the
> > large-folio split counter increased by 1024 even though no swapout
> > progress was possible. Skipping the split in that case kept the counter
> > at 0.
> >
> > This series makes folio_alloc_swap() classify failures according to
> > whether splitting a large folio might allow swapout to make progress.
> > Callers can then avoid destroying the large folio when neither global
> > swap availability nor the folio's memcg swap hierarchy has capacity for
> > even a smaller folio.
>
> Thanks. Sashiko had a question which didn't occur against v6:
>
>
> https://sashiko.dev/#/patchset/20260830-folio_swap_entry-v1-0-7786b52acbc8@columbia.edu
Hi Andrew,
I think the Sashiko link points to a different series:
[PATCH 0/8] mm: remove page_swap_entry()
The Sashiko page for this v7 reports "No issues found":
https://sashiko.dev/#/patchset/20260830042920.2280454-1-xueyuan.chen21@gmail.com
Did you intend to link a different question?
Thanks,
Xueyuan
[-- Attachment #2: Type: text/html, Size: 2444 bytes --]
^ permalink raw reply [flat|nested] 8+ messages in thread