mm/mglru: fix ineffective memory protection for non-kswapd reclaim
Subsystems (5)
PATCH 1/1 mm/mglru: fix ineffective memory protection for non-kswapd reclaim
From: Ridong Chen <[email protected]>
memory.min/low is silently bypassed for MGLRU during global proactive reclaim (writing to the root memory.reclaim) and global direct reclaim. 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 remain stale or zero.
Introduce mem_cgroup_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.
--- include/linux/memcontrol.h | 7 +++++++ mm/memcontrol.c | 42 ++++++++++++++++++++++++++++++++++++++ mm/vmscan.c | 3 +++ 3 files changed, 52 insertions(+)
include/linux/memcontrol.h +7 -0
@@ -600,6 +600,8 @@ static inline void mem_cgroup_protection(struct mem_cgroup *root,void mem_cgroup_calculate_protection (struct mem_cgroup *root,struct mem_cgroup *memcg); +void mem_cgroup_protection_path (struct mem_cgroup *root, +struct mem_cgroup *memcg);static inline bool mem_cgroup_unprotected (struct mem_cgroup *target,struct mem_cgroup *memcg)
@@ -1126,6 +1128,11 @@ static inline void mem_cgroup_calculate_protection(struct mem_cgroup *root, { } +static inline void mem_cgroup_protection_path (struct mem_cgroup *root, +struct mem_cgroup *memcg) +{ +} +static inline bool mem_cgroup_unprotected (struct mem_cgroup *target,struct mem_cgroup *memcg) {
mm/memcontrol.c +42 -0
48 lines not on this page · raw message
mm/vmscan.c +3 -0
@@ -5031,6 +5031,9 @@ static int shrink_one(struct lruvec *lruvec, struct scan_control *sc)struct pglist_data *pgdat =lruvec_pgdat (lruvec);/* lru_gen_age_node() called mem_cgroup_calculate_protection() */ +if (!current_is_kswapd ()) +mem_cgroup_protection_path (NULL , memcg); +if (mem_cgroup_below_min (NULL , memcg))return MEMCG_LRU_YOUNG ;
Signature · 1 line
2.43.0
Quoted 133 lines · Ridong Chen
From: Ridong Chen <[email protected]>
memory.min/low is silently bypassed for MGLRU during global proactive reclaim (writing to the root memory.reclaim) and global direct reclaim. 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 6070272memory.min is 100M, yet reclaim drops a/b down to 6M, breaking the protection. The traditional LRU path is not affected because
shrink_node()callsmem_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 intolru_gen_age_node(), which only runs for kswapd. Non-kswapd global reclaim reachesshrink_one()throughlru_gen_shrink_node()->shrink_many()without any protection computation, so emin/elow remain stale or zero.Introduce
mem_cgroup_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 withmem_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")Signed-off-by Ridong Chen <[email protected]>--- include/linux/memcontrol.h | 7 +++++++ mm/memcontrol.c | 42 ++++++++++++++++++++++++++++++++++++++ mm/vmscan.c | 3 +++ 3 files changed, 52 insertions(+)include/linux/memcontrol.h +7
-0@@ -600,6 +600,8 @@ static inline void mem_cgroup_protection(struct mem_cgroup *root,void mem_cgroup_calculate_protection (struct mem_cgroup *root,struct mem_cgroup *memcg); +void mem_cgroup_protection_path (struct mem_cgroup *root, +struct mem_cgroup *memcg);static inline bool mem_cgroup_unprotected (struct mem_cgroup *target,struct mem_cgroup *memcg)@@ -1126,6 +1128,11 @@ static inline void mem_cgroup_calculate_protection(struct mem_cgroup *root, { } +static inline void mem_cgroup_protection_path (struct mem_cgroup *root, +struct mem_cgroup *memcg) +{ +} +static inline bool mem_cgroup_unprotected (struct mem_cgroup *target,struct mem_cgroup *memcg) {mm/memcontrol.c +42
-0@@ -5134,6 +5134,48 @@ void mem_cgroup_calculate_protection(struct mem_cgroup *root,page_counter_calculate_protection (&root->memory, &memcg->memory, recursive_protection); } +/** +* mem_cgroup_protection_path - compute protection along root->memcg 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_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 (); +} +static int charge_memcg (struct folio *folio,struct mem_cgroup *memcg,gfp_t gfp) {mm/vmscan.c +0
-0
mm/vmscan.c static int shrink_one(struct lruvec *lruvec, struct scan_control *sc) · line 5039
Quoted 9 lines
mm/vmscan.c +3
-0@@ -5031,6 +5031,9 @@ static int shrink_one(struct lruvec *lruvec, struct scan_control *sc)struct pglist_data *pgdat =lruvec_pgdat (lruvec);/* lru_gen_age_node() called mem_cgroup_calculate_protection() */ +if (!current_is_kswapd ()) +mem_cgroup_protection_path (NULL , memcg); +if (mem_cgroup_below_min (NULL , memcg))return MEMCG_LRU_YOUNG ;
Please ignore that, it was sent in error.
Signature · 2 lines
Best regards, Ridong