[LTP] [PATCH v1 2/2] memcontrol03, memcontrol04: Set protection on parent cgroups to prevent kswapd over-reclaim

Wei Gao wegao@suse.com
Sun Oct 4 16:33:27 CEST 2026


On NUMA systems (observed on PowerPC), allocating memory can trigger
node-level memory pressure, waking up kswapd when free pages fall below
the low watermark on a node.

In cgroup v2, both hard protection (memory.min) and best-effort protection
(memory.low) are hierarchical and calculated top-down: effective
protection of descendant cgroups is bounded by their ancestors' protection
values. When LTP runs inside an existing cgroup hierarchy, ancestor cgroups
above the test root have memory.min and memory.low set to 0.

As a result, when kswapd performs node-level background reclaim from the
root cgroup downwards, ancestor cgroups without protection provide no
protection boundary. kswapd reclaims pages from descendant leaf cgroups
regardless of their local memory.min / memory.low settings, causing
memcontrol03 and memcontrol04 to fail with over-reclaim:

NUMA node info:
  NUMA Node 2: CPUs=[2], MemTotal=765 MB, MemFree=610 MB
  NUMA Node 0: CPUs=[0], MemTotal=240 MB, MemFree=93 MB
  NUMA Node 3: CPUs=[3], MemTotal=750 MB, MemFree=419 MB
  NUMA Node 1: CPUs=[1], MemTotal=766 MB, MemFree=95 MB

Error info:
  memcontrol03.c:519: TFAIL: Expect: (A/B/C memory.current=19070976) ~= 34603008

Set memory.min and memory.low on trunk_A and parent cgroups up through the
hierarchy before allocating child pagecache, ensuring protection propagates
down the tree to prevent kswapd over-reclaim under NUMA memory pressure,
and restore the original values upon cleanup.

Signed-off-by: Wei Gao <wegao@suse.com>
---
 .../kernel/controllers/memcg/memcontrol03.c   | 20 ++++++++++++-------
 .../kernel/controllers/memcg/memcontrol04.c   | 19 +++++++++++-------
 2 files changed, 25 insertions(+), 14 deletions(-)

diff --git a/testcases/kernel/controllers/memcg/memcontrol03.c b/testcases/kernel/controllers/memcg/memcontrol03.c
index 25b84c24e..721ae0e46 100644
--- a/testcases/kernel/controllers/memcg/memcontrol03.c
+++ b/testcases/kernel/controllers/memcg/memcontrol03.c
@@ -184,21 +184,24 @@ static void test_memcg_min(void)
 
 	trunk_cg[G] = tst_cg_group_mk(trunk_cg[A], "trunk_G");
 
-	for (i = 0; i < ARRAY_SIZE(leaf_cg); i++) {
+	for (i = 0; i < ARRAY_SIZE(leaf_cg); i++)
 		leaf_cg[i] = tst_cg_group_mk(trunk_cg[B],
 						 "leaf_%c", 'C' + i);
 
-		if (i == E)
-			continue;
-
-		alloc_pagecache_in_child(leaf_cg[i], MB(50));
-	}
-
+	SAFE_CG_PRINT(trunk_cg[A], "memory.min", "50M");
 	SAFE_CG_PRINT(trunk_cg[B], "memory.min", "50M");
 	SAFE_CG_PRINT(leaf_cg[C], "memory.min", "75M");
 	SAFE_CG_PRINT(leaf_cg[D], "memory.min", "25M");
 	SAFE_CG_PRINT(leaf_cg[E], "memory.min", "500M");
 	SAFE_CG_PRINT(leaf_cg[F], "memory.min", "0");
+	set_parent_memory_knob("memory.min", "50M");
+
+	for (i = 0; i < ARRAY_SIZE(leaf_cg); i++) {
+		if (i == E)
+			continue;
+
+		alloc_pagecache_in_child(leaf_cg[i], MB(50));
+	}
 
 	for (attempts = 0; attempts < 5; attempts++) {
 		SAFE_CG_SCANF(trunk_cg[B], "memory.current", "%ld", c);
@@ -223,6 +226,7 @@ static void test_memcg_min(void)
 		     "(A/B/D memory.current=%ld) ~= %d", c[1], MB(21));
 	TST_EXP_EXPR(values_close(c[2], 0, 1),
 		     "(A/B/E memory.current=%ld) ~= 0", c[2]);
+	tst_res(TINFO, "A/B/F memory.current=%ld", c[F]);
 
 	alloc_anon_in_child(trunk_cg[G], MB(170), 1);
 
@@ -231,12 +235,14 @@ static void test_memcg_min(void)
 		     "(A/B memory.current=%ld) ~= %d", c[0], MB(50));
 
 	cleanup_sub_groups();
+	restore_parent_memory_knob("memory.min");
 	SAFE_CLOSE(fd);
 	SAFE_UNLINK(TMPDIR"/tmpfile");
 }
 
 static void cleanup(void)
 {
+	restore_parent_memory_knob("memory.min");
 	cleanup_sub_groups();
 	if (fd > -1)
 		SAFE_CLOSE(fd);
diff --git a/testcases/kernel/controllers/memcg/memcontrol04.c b/testcases/kernel/controllers/memcg/memcontrol04.c
index f1e136df4..10de95bec 100644
--- a/testcases/kernel/controllers/memcg/memcontrol04.c
+++ b/testcases/kernel/controllers/memcg/memcontrol04.c
@@ -157,21 +157,24 @@ static void test_memcg_low(void)
 
 	trunk_cg[G] = tst_cg_group_mk(trunk_cg[A], "trunk_G");
 
-	for (i = 0; i < ARRAY_SIZE(leaf_cg); i++) {
+	for (i = 0; i < ARRAY_SIZE(leaf_cg); i++)
 		leaf_cg[i] = tst_cg_group_mk(trunk_cg[B],
 						 "leaf_%c", 'C' + i);
 
-		if (i == E)
-			continue;
-
-		alloc_pagecache_in_child(leaf_cg[i], MB(50));
-	}
-
+	SAFE_CG_PRINT(trunk_cg[A], "memory.low", "50M");
 	SAFE_CG_PRINT(trunk_cg[B], "memory.low", "50M");
 	SAFE_CG_PRINT(leaf_cg[C], "memory.low", "75M");
 	SAFE_CG_PRINT(leaf_cg[D], "memory.low", "25M");
 	SAFE_CG_PRINT(leaf_cg[E], "memory.low", "500M");
 	SAFE_CG_PRINT(leaf_cg[F], "memory.low", "0");
+	set_parent_memory_knob("memory.low", "50M");
+
+	for (i = 0; i < ARRAY_SIZE(leaf_cg); i++) {
+		if (i == E)
+			continue;
+
+		alloc_pagecache_in_child(leaf_cg[i], MB(50));
+	}
 
 	alloc_anon_in_child(trunk_cg[G], MB(148));
 
@@ -230,12 +233,14 @@ static void test_memcg_low(void)
 	}
 
 	cleanup_sub_groups();
+	restore_parent_memory_knob("memory.low");
 	SAFE_CLOSE(fd);
 	SAFE_UNLINK(TMPDIR"/tmpfile");
 }
 
 static void cleanup(void)
 {
+	restore_parent_memory_knob("memory.low");
 	cleanup_sub_groups();
 	if (fd > -1)
 		SAFE_CLOSE(fd);
-- 
2.55.0



More information about the ltp mailing list