mm: vmscan: fix node reclaim ignoring swappiness parameter
Subsystems (7)
cover mm: vmscan: fix node reclaim ignoring swappiness parameter
From: Ridong Chen <[email protected]>
The per-node proactive reclaim interface (/sys/devices/system/node/nodeX/reclaim) accepts a swappiness parameter, but it is silently ignored when CONFIG_MEMCG is disabled. The root cause is that sc_swappiness() has separate implementations for CONFIG_MEMCG and !CONFIG_MEMCG, and the latter never checks proactive_swappiness.
Patch 1 moves mem_cgroup_swappiness() and vm_swappiness out of the public include/linux/swap.h into the mm-private mm/swap.h, and makes the helper handle both CONFIG_MEMCG and !CONFIG_MEMCG in a single inline function. This is a prerequisite for unifying sc_swappiness().
Patch 2 consolidates sc_swappiness() into a single definition that works regardless of CONFIG_MEMCG, fixing the node reclaim swappiness bug.
---
v3 -> v4:
- READ_ONCE() was missed when reading memcg->swappiness, add it back.
v2 -> v3:
- Simplify mem_cgroup_swappiness as suggested by Johannes.
- Rebase on the next-20260722
v1 -> v2:
- Move mem_cgroup_swappiness() and vm_swappiness to mm/swap.h instead of include/linux/memcontrol.h. Suggested by Barry Song.
- Correct fix tag for patch 2.
v1: https://lore.kernel.org/all/[email protected]/
Ridong Chen (2):
memcg: move mem_cgroup_swappiness and vm_swappiness to mm/swap.h
mm: vmscan: fix node reclaim ignoring swappiness parameter
include/linux/memcontrol.h | 4 ++-- include/linux/swap.h | 19 ------------------- mm/memcontrol.c | 4 ++-- mm/swap.h | 13 +++++++++++++ mm/vmscan.c | 19 +++++++------------ 5 files changed, 24 insertions(+), 35 deletions(-)
Signature · 1 line
2.34.1
PATCH 1/2 memcg: move mem_cgroup_swappiness and vm_swappiness to mm/swap.h
From: Ridong Chen <[email protected]>
The per-memcg swappiness knob is v1-only; v2 always uses global vm_swappiness and ignores the per-cgroup field.
Both mem_cgroup_swappiness() and vm_swappiness are only used within mm/ (memcontrol.c, memcontrol-v1.c, vmscan.c), so move them out of the public include/linux/swap.h into the mm-private mm/swap.h. This keeps unrelated declarations out of include/linux/swap.h.
Guard memcg->swappiness with CONFIG_MEMCG_V1 as well, so v2-only kernels drop the unused field.
No functional change for v1; v2-only kernels drop the unused field.
--- include/linux/memcontrol.h | 4 ++-- include/linux/swap.h | 19 ------------------- mm/memcontrol.c | 4 ++-- mm/swap.h | 13 +++++++++++++ 4 files changed, 17 insertions(+), 23 deletions(-)
include/linux/memcontrol.h +2 -2
@@ -239,8 +239,6 @@ struct mem_cgroup {*/ bool oom_group; -int swappiness; -/* memory.events and memory.events.local */ struct cgroup_file events_file;struct cgroup_file events_local_file;
@@ -318,6 +316,8 @@ struct mem_cgroup {/* List of events which userspace want to receive */ struct list_head event_list;spinlock_t event_list_lock; + +int swappiness;#endif /* CONFIG_MEMCG_V1 */ struct mem_cgroup_per_node *nodeinfo[];
include/linux/swap.h +0 -19
@@ -309,7 +309,6 @@ static inline bool lru_cache_disabled(void) }extern unsigned long shrink_all_memory (unsigned long nr_pages); -extern int vm_swappiness;long remove_mapping (struct address_space *mapping,struct folio *folio);#if defined(CONFIG_SYSFS ) && defined(CONFIG_NUMA )
@@ -473,25 +472,7 @@ static inline int add_swap_extent(struct swap_info_struct *sis, }#endif /* CONFIG_SWAP */ #ifdef CONFIG_MEMCG -static inline int mem_cgroup_swappiness (struct mem_cgroup *memcg) -{ -/* Cgroup2 doesn't have per-cgroup swappiness */ -if (cgroup_subsys_on_dfl (memory_cgrp_subsys)) -return READ_ONCE (vm_swappiness); - -/* root ? */ -if (mem_cgroup_disabled () ||mem_cgroup_is_root (memcg)) -return READ_ONCE (vm_swappiness); - -return READ_ONCE (memcg->swappiness); -} -void lru_reparent_memcg (struct mem_cgroup *memcg,struct mem_cgroup *parent,int nid); -#else -static inline int mem_cgroup_swappiness (struct mem_cgroup *memcg) -{ -return READ_ONCE (vm_swappiness); -}#endif #if defined(CONFIG_SWAP ) && defined(CONFIG_MEMCG ) && defined(CONFIG_BLK_CGROUP )
mm/memcontrol.c +2 -2
@@ -63,6 +63,7 @@#include <linux/sched/isolation.h> #include <linux/kmemleak.h> #include "internal.h" +#include "swap.h" #include "swap_table.h" #include <net/sock.h> #include <net/ip.h>
@@ -4176,11 +4177,10 @@ mem_cgroup_css_alloc(struct cgroup_subsys_state *parent_css)#endif page_counter_set_high (&memcg->swap,PAGE_COUNTER_MAX );if (parent) { -WRITE_ONCE (memcg->swappiness,mem_cgroup_swappiness (parent)); -page_counter_init (&memcg->memory, &parent->memory, memcg_on_dfl);page_counter_init (&memcg->swap, &parent->swap, false);#ifdef CONFIG_MEMCG_V1 +WRITE_ONCE (memcg->swappiness,mem_cgroup_swappiness (parent)); memcg->memory.track_failcnt = !memcg_on_dfl;WRITE_ONCE (memcg->oom_kill_disable,READ_ONCE (parent->oom_kill_disable));page_counter_init (&memcg->kmem, &parent->kmem, false);
mm/swap.h +13 -0
@@ -4,6 +4,7 @@#include <linux/atomic.h> /* for atomic_long_t */ #include <linux/mm.h> /* for PAGE_SHIFT */ +#include <linux/memcontrol.h> /* for mem_cgroup_swappiness() */ struct mempolicy ;struct swap_iocb ;
@@ -99,6 +100,18 @@ struct swap_ops {void (*submit_read)(struct swap_io_ctx *ctx); }; +extern int vm_swappiness; + +static inline int mem_cgroup_swappiness (struct mem_cgroup *memcg) +{ +#ifdef CONFIG_MEMCG_V1 +if (!cgroup_subsys_on_dfl (memory_cgrp_subsys) && + !mem_cgroup_disabled () && !mem_cgroup_is_root (memcg)) +return READ_ONCE (memcg->swappiness); +#endif +return READ_ONCE (vm_swappiness); +} +#ifdef CONFIG_SWAP #include <linux/swapops.h> /* for swp_offset */ #include <linux/blk_types.h> /* for bio_end_io_t */
Signature · 1 line
2.34.1
Re: [PATCH -v4 1/2] memcg: move mem_cgroup_swappiness and vm_swappiness to mm/swap.h
Quoted 13 lines · Ridong
From: Ridong Chen <[email protected]>
The per-memcg swappiness knob is v1-only; v2 always uses global
vm_swappinessand ignores the per-cgroup field.Both
mem_cgroup_swappiness()andvm_swappinessare only used withinmm/(memcontrol.c,memcontrol-v1.c,vmscan.c), so move them out of the publicinclude/linux/swap.hinto the mm-privatemm/swap.h. This keeps unrelated declarations out ofinclude/linux/swap.h.Guard
memcg->swappinesswithCONFIG_MEMCG_V1as well, so v2-only kernels drop the unused field.
Quoted 6 lines
No functional change for v1; v2-only kernels drop the unused field.
Acked-by Johannes Weiner <[email protected]>Reviewed-by Barry Song <[email protected]>Reviewed-by Song Hu <[email protected]>Signed-off-by Ridong Chen <[email protected]>
PATCH 2/2 mm: vmscan: fix node reclaim ignoring swappiness parameter
In reply to RidongFrom: Ridong Chen <[email protected]>
sc_swappiness() had two separate definitions depending on CONFIG_MEMCG. The !CONFIG_MEMCG variant simply returned vm_swappiness, ignoring the proactive_swappiness value passed through scan_control. This caused the swappiness parameter written to /sys/devices/system/node/nodeX/reclaim to have no effect when CONFIG_MEMCG is disabled.
Fix this by consolidating sc_swappiness() into a single definition that checks sc->proactive_swappiness first, then falls back to mem_cgroup_swappiness() which already handles both CONFIG_MEMCG and !CONFIG_MEMCG.
Before fix (swappiness=max ignored, mostly file pages reclaimed):
# cat /proc/sys/vm/swappiness
60
# cat /proc/vmstat | grep pgsteal
pgsteal_kswapd 0
pgsteal_direct 0
pgsteal_khugepaged 0
pgsteal_proactive 1840
pgsteal_anon 25
pgsteal_file 1815
# echo "64M swappiness=max" > /sys/devices/system/node/node0/reclaim
# cat /proc/vmstat | grep pgsteal
pgsteal_kswapd 0
pgsteal_direct 0
pgsteal_khugepaged 0
pgsteal_proactive 18013
pgsteal_anon 337
pgsteal_file 17676After fix (swappiness=max honored, anon pages reclaimed as expected):
# cat /proc/vmstat | grep pgsteal
pgsteal_kswapd 0
pgsteal_direct 0
pgsteal_khugepaged 0
pgsteal_proactive 0
pgsteal_anon 0
pgsteal_file 0
# echo "64M swappiness=max" > /sys/devices/system/node/node0/reclaim
# cat /proc/vmstat | grep pgsteal
pgsteal_kswapd 0
pgsteal_direct 0
pgsteal_khugepaged 0
pgsteal_proactive 16283
pgsteal_anon 16283
pgsteal_file 0--- mm/vmscan.c | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-)
mm/vmscan.c +7 -12
@@ -199,6 +199,13 @@ struct scan_control {*/ int vm_swappiness =60 ; +static int sc_swappiness (struct scan_control *sc,struct mem_cgroup *memcg) +{ +if (sc->proactive && sc->proactive_swappiness) +return *sc->proactive_swappiness; +return mem_cgroup_swappiness (memcg); +} +#ifdef CONFIG_MEMCG /* Returns true for reclaim through cgroup limits or cgroup interfaces. */
@@ -239,13 +246,6 @@ static bool writeback_throttling_sane(struct scan_control *sc)#endif return false; } - -static int sc_swappiness (struct scan_control *sc,struct mem_cgroup *memcg) -{ -if (sc->proactive && sc->proactive_swappiness) -return *sc->proactive_swappiness; -return mem_cgroup_swappiness (memcg); -}#else static bool cgroup_reclaim (struct scan_control *sc) {
@@ -261,11 +261,6 @@ static bool writeback_throttling_sane(struct scan_control *sc) {return true; } - -static int sc_swappiness (struct scan_control *sc,struct mem_cgroup *memcg) -{ -return READ_ONCE (vm_swappiness); -}#endif static void set_task_reclaim_state(struct task_struct *task,
Signature · 1 line
2.34.1
Quoted 52 lines · Ridong
From: Ridong Chen <[email protected]>
sc_swappiness()had two separate definitions depending onCONFIG_MEMCG. The !CONFIG_MEMCG variant simply returnedvm_swappiness, ignoring theproactive_swappinessvalue passed throughscan_control. This caused the swappiness parameter written to/sys/devices/system/node/nodeX/reclaimto have no effect whenCONFIG_MEMCGis disabled.Fix this by consolidating
sc_swappiness()into a single definition that checkssc->proactive_swappinessfirst, then falls back tomem_cgroup_swappiness()which already handles bothCONFIG_MEMCGand !CONFIG_MEMCG.Before fix (swappiness=max ignored, mostly file pages reclaimed):
# cat /proc/sys/vm/swappiness 60 # cat /proc/vmstat | grep pgsteal pgsteal_kswapd 0 pgsteal_direct 0 pgsteal_khugepaged 0 pgsteal_proactive 1840 pgsteal_anon 25 pgsteal_file 1815 # echo "64M swappiness=max" > /sys/devices/system/node/node0/reclaim # cat /proc/vmstat | grep pgsteal pgsteal_kswapd 0 pgsteal_direct 0 pgsteal_khugepaged 0 pgsteal_proactive 18013 pgsteal_anon 337 pgsteal_file 17676After fix (swappiness=max honored, anon pages reclaimed as expected):
# cat /proc/vmstat | grep pgsteal pgsteal_kswapd 0 pgsteal_direct 0 pgsteal_khugepaged 0 pgsteal_proactive 0 pgsteal_anon 0 pgsteal_file 0 # echo "64M swappiness=max" > /sys/devices/system/node/node0/reclaim # cat /proc/vmstat | grep pgsteal pgsteal_kswapd 0 pgsteal_direct 0 pgsteal_khugepaged 0 pgsteal_proactive 16283 pgsteal_anon 16283 pgsteal_file 0
Quoted 8 lines
Cc [email protected] # 6.17+Fixes b980077899ea ("mm: introduce per-node proactive reclaim interface")Acked-by Johannes Weiner <[email protected]>Reviewed-by Barry Song <[email protected]>Acked-by Qi Zheng <[email protected]>Tested-by Song Hu <[email protected]>Reviewed-by Song Hu <[email protected]>Signed-off-by Ridong Chen <[email protected]>
Andrew Morton
maintainerQuoted 8 lines · On Thu, 23 Jul 2026 11:24:32 +0800 Ridong <[email protected]> wrote:
From: Ridong Chen <[email protected]>
The per-node proactive reclaim interface (
/sys/devices/system/node/nodeX/reclaim) accepts a swappiness parameter, but it is silently ignored whenCONFIG_MEMCGis disabled. The root cause is thatsc_swappiness()has separate implementations forCONFIG_MEMCGand !CONFIG_MEMCG, and the latter never checksproactive_swappiness.
Quoted 7 lines
Patch 1 moves
mem_cgroup_swappiness()andvm_swappinessout of the publicinclude/linux/swap.hinto the mm-privatemm/swap.h, and makes the helper handle bothCONFIG_MEMCGand !CONFIG_MEMCG in a single inline function. This is a prerequisite for unifyingsc_swappiness().Patch 2 consolidates
sc_swappiness()into a single definition that works regardless ofCONFIG_MEMCG, fixing the node reclaim swappiness bug.
Thanks, I updated mm.git to this version.
Quoted 6 lines
v3 -> v4:
-READ_ONCE()was missed when readingmemcg->swappiness, add it back.v2 -> v3:
- Simplifymem_cgroup_swappinessas suggested by Johannes.
- Rebase on the next-20260722
Here's how v3 plus v4 altered mm.git:
mm/swap.h | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-)
mm/swap.h renamed from mm/swap.h~b +4 -11
@@ -105,18 +105,11 @@ extern int vm_swappiness;static inline int mem_cgroup_swappiness (struct mem_cgroup *memcg) {#ifdef CONFIG_MEMCG_V1 -/* Cgroup2 doesn't have per-cgroup swappiness */ -if (cgroup_subsys_on_dfl (memory_cgrp_subsys)) -return READ_ONCE (vm_swappiness); - -/* root ? */ -if (mem_cgroup_disabled () ||mem_cgroup_is_root (memcg)) -return READ_ONCE (vm_swappiness); - -return READ_ONCE (memcg->swappiness); -#else -return READ_ONCE (vm_swappiness); +if (!cgroup_subsys_on_dfl (memory_cgrp_subsys) && + !mem_cgroup_disabled () && !mem_cgroup_is_root (memcg)) +return READ_ONCE (memcg->swappiness);#endif +return READ_ONCE (vm_swappiness); }#ifdef CONFIG_SWAP
_
Quoted 15 lines · Andrew Morton
Quoted 13 lines · On Thu, 23 Jul 2026 11:24:32 +0800 Ridong <[email protected]> wrote:
From: Ridong Chen <[email protected]>
The per-node proactive reclaim interface (
/sys/devices/system/node/nodeX/reclaim) accepts a swappiness parameter, but it is silently ignored whenCONFIG_MEMCGis disabled. The root cause is thatsc_swappiness()has separate implementations forCONFIG_MEMCGand !CONFIG_MEMCG, and the latter never checksproactive_swappiness.Patch 1 moves
mem_cgroup_swappiness()andvm_swappinessout of the publicinclude/linux/swap.hinto the mm-privatemm/swap.h, and makes the helper handle bothCONFIG_MEMCGand !CONFIG_MEMCG in a single inline function. This is a prerequisite for unifyingsc_swappiness().
Quoted 5 lines
Quoted 2 lines
Patch 2 consolidates
sc_swappiness()into a single definition that works regardless ofCONFIG_MEMCG, fixing the node reclaim swappiness bug.Thanks, I updated mm.git to this version.
Thanks.
Signature · 2 lines
Best regards Ridong