* [PATCH v4 0/2] mm/mglru: fix ineffective memory protection for non-kswapd reclaim
@ 2026-09-07 2:54 Ridong Chen
2026-09-07 2:54 ` [PATCH v4 1/2] mm/page_counter: avoid integer overflow in effective_protection() Ridong Chen
2026-09-07 2:54 ` [PATCH v4 2/2] mm/mglru: fix ineffective memory protection for non-kswapd reclaim Ridong Chen
0 siblings, 2 replies; 3+ messages in thread
From: Ridong Chen @ 2026-09-07 2:54 UTC (permalink / raw)
To: Johannes Weiner, Michal Hocko, Roman Gushchin, Shakeel Butt,
Andrew Morton
Cc: Muchun Song, David Hildenbrand, Qi Zheng, Lorenzo Stoakes,
Kairui Song, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
Chris Down, Tejun Heo, Yu Zhao,
open list:CONTROL GROUP - MEMORY RESOURCE CONTROLLER (MEMCG),
open list:CONTROL GROUP - MEMORY RESOURCE CONTROLLER (MEMCG),
linux-kernel, Ridong Chen, Ridong Chen
From: Ridong Chen <chenridong@xiaomi.com>
For MGLRU, memory.min/low is not honored during non-kswapd global
reclaim (global direct reclaim and root-level memory.reclaim), because
these paths shrink memcgs using stale protection (emin/elow).
Patch 2 is the actual fix. Patch 1 is a prerequisite: an integer overflow
in effective_protection(), spotted by the sashiko review tool, which
patch 2's new caller would also be exposed to.
---
v4:
- Per Barry's and Johannes's review comments.
v3:
- Per Barry Song's review comments
v2:
- Fix an issue caused by non-atomic read races in patch 1 [1]
[1] https://sashiko.dev/#/patchset/20260828092432.1257917-1-ridong.chen@linux.dev?part=1
Ridong Chen (2):
mm/page_counter: avoid integer overflow in effective_protection()
mm/mglru: fix ineffective memory protection for non-kswapd reclaim
include/linux/memcontrol.h | 10 +++++++++
mm/memcontrol.c | 45 ++++++++++++++++++++++++++++++++++++++
mm/page_counter.c | 21 +++++++++++++-----
mm/vmscan.c | 8 ++++++-
4 files changed, 77 insertions(+), 7 deletions(-)
--
2.34.1
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH v4 1/2] mm/page_counter: avoid integer overflow in effective_protection()
2026-09-07 2:54 [PATCH v4 0/2] mm/mglru: fix ineffective memory protection for non-kswapd reclaim Ridong Chen
@ 2026-09-07 2:54 ` Ridong Chen
2026-09-07 2:54 ` [PATCH v4 2/2] mm/mglru: fix ineffective memory protection for non-kswapd reclaim Ridong Chen
1 sibling, 0 replies; 3+ messages in thread
From: Ridong Chen @ 2026-09-07 2:54 UTC (permalink / raw)
To: Johannes Weiner, Michal Hocko, Roman Gushchin, Shakeel Butt,
Andrew Morton
Cc: Muchun Song, David Hildenbrand, Qi Zheng, Lorenzo Stoakes,
Kairui Song, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
Chris Down, Tejun Heo, Yu Zhao,
open list:CONTROL GROUP - MEMORY RESOURCE CONTROLLER (MEMCG),
open list:CONTROL GROUP - MEMORY RESOURCE CONTROLLER (MEMCG),
linux-kernel, Ridong Chen, Ridong Chen, stable
From: Ridong Chen <chenridong@xiaomi.com>
effective_protection() scales a parent's protection by a ratio of page
counts, e.g. for recursive protection:
(parent_effective - siblings_protected) * (usage - protected)
/ (parent_usage - siblings_protected)
The multiply is done at unsigned long width before dividing. On systems
with >= 16TB RAM the product can exceed 2^64 and wrap, giving a bogus
protection value and silently breaking memory.min/low enforcement.
Use mul_u64_u64_div_u64() to multiply in a 128-bit intermediate. Because
usage and parent_usage are not read atomically (a child is charged
before its parent), usage - protected can briefly exceed the divisor,
making the quotient overflow 64 bits and trap (#DE on x86). Cap it so
the ratio stays <= 1.
Reported by the sashiko review tool [1].
[1] https://sashiko.dev/#/patchset/20260826133054.88529-1-ridong.chen@linux.dev?part=1
Fixes: bc50bcc6e00b ("mm: memcontrol: clean up and document effective low/min calculations")
Fixes: 8a931f801340 ("mm: memcontrol: recursive memory.low protection")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-8
Reviewed-by: Barry Song <baohua@kernel.org>
Reviewed-by: Johannes Weiner <hannes@cmpxchg.org>
Signed-off-by: Ridong Chen <chenridong@xiaomi.com>
---
mm/page_counter.c | 21 +++++++++++++++------
1 file changed, 15 insertions(+), 6 deletions(-)
diff --git a/mm/page_counter.c b/mm/page_counter.c
index 661e0f2a5127a..ea0d1646cff85 100644
--- a/mm/page_counter.c
+++ b/mm/page_counter.c
@@ -8,6 +8,7 @@
#include <linux/page_counter.h>
#include <linux/atomic.h>
#include <linux/kernel.h>
+#include <linux/math64.h>
#include <linux/string.h>
#include <linux/sched.h>
#include <linux/bug.h>
@@ -356,7 +357,8 @@ static unsigned long effective_protection(unsigned long usage,
* otherwise get a smaller chunk than what they claimed.
*/
if (siblings_protected > parent_effective)
- return protected * parent_effective / siblings_protected;
+ return mul_u64_u64_div_u64(protected, parent_effective,
+ siblings_protected);
/*
* Ok, utilized protection of all children is within what the
@@ -397,13 +399,20 @@ static unsigned long effective_protection(unsigned long usage,
if (parent_effective > siblings_protected &&
parent_usage > siblings_protected &&
usage > protected) {
- unsigned long unclaimed;
+ unsigned long parent_unclaimed, parent_unprotected, unprotected;
- unclaimed = parent_effective - siblings_protected;
- unclaimed *= usage - protected;
- unclaimed /= parent_usage - siblings_protected;
+ parent_unclaimed = parent_effective - siblings_protected;
+ parent_unprotected = parent_usage - siblings_protected;
- ep += unclaimed;
+ /*
+ * The usages aren't read atomically, so a child can transiently
+ * appear to use more than its parent, making the ratio exceed 1
+ * and the quotient overflow 64 bits (#DE on x86). Cap it.
+ */
+ unprotected = min(usage - protected, parent_unprotected);
+
+ ep += mul_u64_u64_div_u64(parent_unclaimed, unprotected,
+ parent_unprotected);
}
return ep;
--
2.34.1
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH v4 2/2] mm/mglru: fix ineffective memory protection for non-kswapd reclaim
2026-09-07 2:54 [PATCH v4 0/2] mm/mglru: fix ineffective memory protection for non-kswapd reclaim Ridong Chen
2026-09-07 2:54 ` [PATCH v4 1/2] mm/page_counter: avoid integer overflow in effective_protection() Ridong Chen
@ 2026-09-07 2:54 ` Ridong Chen
1 sibling, 0 replies; 3+ messages in thread
From: Ridong Chen @ 2026-09-07 2:54 UTC (permalink / raw)
To: Johannes Weiner, Michal Hocko, Roman Gushchin, Shakeel Butt,
Andrew Morton
Cc: Muchun Song, David Hildenbrand, Qi Zheng, Lorenzo Stoakes,
Kairui Song, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
Chris Down, Tejun Heo, Yu Zhao,
open list:CONTROL GROUP - MEMORY RESOURCE CONTROLLER (MEMCG),
open list:CONTROL GROUP - MEMORY RESOURCE CONTROLLER (MEMCG),
linux-kernel, Ridong Chen, Ridong Chen, stable
From: Ridong Chen <chenridong@xiaomi.com>
For MGLRU, memory.min/low is not honored during global proactive reclaim
(writing to the root memory.reclaim) and global direct reclaim, because
these paths shrink memcgs using stale protection (emin/elow).
It can be reproduced as follows:
# echo 7 > /sys/kernel/mm/lru_gen/enabled
# cd /sys/fs/cgroup
# mkdir -p a/b
# echo 100M > a/memory.min
# echo +memory > a/cgroup.subtree_control
# echo 100M > a/b/memory.min
# echo $$ > a/b/cgroup.procs
# dd if=/dev/zero of=/tmp/testfile bs=1M count=200
# cat a/b/memory.current
222650368
# echo 500M > memory.reclaim
-bash: echo: write error: Resource temporarily unavailable
# cat a/b/memory.current
6070272
memory.min is 100M, yet reclaim drops a/b down to 6M, breaking the
protection. The traditional LRU path is not affected because
shrink_node() calls mem_cgroup_calculate_protection() for each memcg it
visits during a top-down tree walk.
Commit 30d77b7eef01 ("mm/mglru: fix ineffective protection calculation")
moved the protection computation into lru_gen_age_node(), which only
runs for kswapd. Non-kswapd global reclaim reaches shrink_one() through
lru_gen_shrink_node() -> shrink_many() without any protection
computation, so emin/elow are whatever a previous kswapd run left behind
- or zero if kswapd never ran on this node. Relying on a prior kswapd
pass is not correct either: a memcg's emin/elow are derived from its
ancestors' memory.min/low settings and from children_min_usage, both of
which change over time, so emin/elow go stale even after kswapd has run
and must be recomputed at the point of reclaim.
Introduce mem_cgroup_calculate_protection_path() which computes emin/elow
along the root-to-target path only, by iterating through the cgroup
ancestors array top-down. This avoids the full tree traversal that would
be needed with mem_cgroup_calculate_protection(), limiting the cost to
O(depth) per memcg - typically 3-5 levels.
Call it from shrink_one() for the non-kswapd path so that each memcg
about to be shrunk has correct protection values.
Fixes: e4dde56cd208 ("mm: multi-gen LRU: per-node lru_gen_folio lists")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-8
Reviewed-by: Barry Song <baohua@kernel.org>
Reviewed-by: Johannes Weiner <hannes@cmpxchg.org>
Signed-off-by: Ridong Chen <chenridong@xiaomi.com>
---
include/linux/memcontrol.h | 10 +++++++++
mm/memcontrol.c | 45 ++++++++++++++++++++++++++++++++++++++
mm/vmscan.c | 8 ++++++-
3 files changed, 62 insertions(+), 1 deletion(-)
diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
index f227348a3f24a..a65a516adc665 100644
--- a/include/linux/memcontrol.h
+++ b/include/linux/memcontrol.h
@@ -1885,6 +1885,16 @@ static inline bool memcg_is_dying(struct mem_cgroup *memcg)
}
#endif /* CONFIG_MEMCG */
+#if defined(CONFIG_MEMCG) && defined(CONFIG_LRU_GEN)
+void mem_cgroup_calculate_protection_path(struct mem_cgroup *root,
+ struct mem_cgroup *memcg);
+#else
+static inline void mem_cgroup_calculate_protection_path(struct mem_cgroup *root,
+ struct mem_cgroup *memcg)
+{
+}
+#endif
+
#if defined(CONFIG_MEMCG) && defined(CONFIG_ZSWAP)
bool obj_cgroup_may_zswap(struct obj_cgroup *objcg);
void obj_cgroup_charge_zswap(struct obj_cgroup *objcg, size_t size);
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index 256b68ffca70e..ae568fc688130 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -5214,6 +5214,51 @@ void mem_cgroup_calculate_protection(struct mem_cgroup *root,
page_counter_calculate_protection(&root->memory, &memcg->memory, recursive_protection);
}
+#ifdef CONFIG_LRU_GEN
+/**
+ * mem_cgroup_calculate_protection_path - compute protection along a path
+ * @root: the top ancestor of the sub-tree being checked (NULL for root_mem_cgroup)
+ * @memcg: the target memory cgroup
+ *
+ * Walk the ancestor path from @root down to @memcg and compute the effective
+ * protection at each level. This is safe for isolated queries because it
+ * ensures parents are computed before children.
+ */
+void mem_cgroup_calculate_protection_path(struct mem_cgroup *root,
+ struct mem_cgroup *memcg)
+{
+ bool recursive_protection =
+ cgrp_dfl_root.flags & CGRP_ROOT_MEMORY_RECURSIVE_PROT;
+ struct cgroup *cg;
+ int root_level, i;
+
+ if (mem_cgroup_disabled())
+ return;
+
+ if (!root)
+ root = root_mem_cgroup;
+
+ if (memcg == root)
+ return;
+
+ root_level = root->css.cgroup->level;
+ cg = memcg->css.cgroup;
+
+ rcu_read_lock();
+ for (i = root_level + 1; i <= cg->level; i++) {
+ struct mem_cgroup *cur;
+
+ cur = mem_cgroup_from_css(cgroup_css(cg->ancestors[i],
+ &memory_cgrp_subsys));
+ if (cur)
+ page_counter_calculate_protection(&root->memory,
+ &cur->memory,
+ recursive_protection);
+ }
+ rcu_read_unlock();
+}
+#endif /* CONFIG_LRU_GEN */
+
static int charge_memcg(struct folio *folio, struct mem_cgroup *memcg,
gfp_t gfp)
{
diff --git a/mm/vmscan.c b/mm/vmscan.c
index b4c9b8f3dfe99..8409ea4bbf379 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -5111,7 +5111,13 @@ static int shrink_one(struct lruvec *lruvec, struct scan_control *sc)
struct mem_cgroup *memcg = lruvec_memcg(lruvec);
struct pglist_data *pgdat = lruvec_pgdat(lruvec);
- /* lru_gen_age_node() called mem_cgroup_calculate_protection() */
+ /*
+ * For kswapd, mem_cgroup_calculate_protection() has already
+ * been called during the top-down cgroup traversal.
+ */
+ if (!current_is_kswapd())
+ mem_cgroup_calculate_protection_path(NULL, memcg);
+
if (mem_cgroup_below_min(NULL, memcg))
return MEMCG_LRU_YOUNG;
--
2.34.1
^ permalink raw reply related [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-09-07 2:55 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-07 2:54 [PATCH v4 0/2] mm/mglru: fix ineffective memory protection for non-kswapd reclaim Ridong Chen
2026-09-07 2:54 ` [PATCH v4 1/2] mm/page_counter: avoid integer overflow in effective_protection() Ridong Chen
2026-09-07 2:54 ` [PATCH v4 2/2] mm/mglru: fix ineffective memory protection for non-kswapd reclaim Ridong Chen
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.