[LTP] [PATCH] memcg/memcontrol05: add cgroup v2 task migration charge accounting test
Pavithra
pavrampu@linux.ibm.com
Tue Aug 4 11:59:25 CEST 2026
Verify that in cgroup v2, memory charges remain pinned to the cgroup
that allocated them when a task migrates, and that new allocations after
migration are charged to the destination cgroup.
Signed-off-by: Pavithra <pavrampu@linux.ibm.com>
---
runtest/controllers | 1 +
testcases/kernel/controllers/memcg/.gitignore | 1 +
.../kernel/controllers/memcg/memcontrol05.c | 160 ++++++++++++++++++
3 files changed, 162 insertions(+)
create mode 100644 testcases/kernel/controllers/memcg/memcontrol05.c
diff --git a/runtest/controllers b/runtest/controllers
index 93c52c439..a9050ef0e 100644
--- a/runtest/controllers
+++ b/runtest/controllers
@@ -24,6 +24,7 @@ memcontrol01 memcontrol01
memcontrol02 memcontrol02
memcontrol03 memcontrol03
memcontrol04 memcontrol04
+memcontrol05 memcontrol05
cgroup_fj_function_debug cgroup_fj_function.sh debug
cgroup_fj_function_cpuset cgroup_fj_function.sh cpuset
diff --git a/testcases/kernel/controllers/memcg/.gitignore b/testcases/kernel/controllers/memcg/.gitignore
index 3883cede6..8b9f6005c 100644
--- a/testcases/kernel/controllers/memcg/.gitignore
+++ b/testcases/kernel/controllers/memcg/.gitignore
@@ -9,3 +9,4 @@ memcontrol01
memcontrol02
memcontrol03
memcontrol04
+memcontrol05
diff --git a/testcases/kernel/controllers/memcg/memcontrol05.c b/testcases/kernel/controllers/memcg/memcontrol05.c
new file mode 100644
index 000000000..96621c478
--- /dev/null
+++ b/testcases/kernel/controllers/memcg/memcontrol05.c
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026 IBM
+ * Author : Pavithra <pavrampu@linux.ibm.com>
+ */
+
+/*\
+ * Origin: testcases/kernel/controllers/memctl/memctl_testplan.txt
+ * Verify memory controller charge accounting during task migration.
+ *
+ * Two child cgroups (group_a, group_b) are created under the test cgroup.
+ * A worker process is placed in group_a and allocates a known amount of
+ * anonymous memory, touching every page to ensure physical allocation.
+ *
+ * Test 1 - No charge transfer on migration:
+ * The worker is migrated to group_b by writing its PID to
+ * group_b/cgroup.procs. In cgroup v2 memory charges are never
+ * transferred on task migration; they remain pinned to the cgroup
+ * that allocated them. group_b/memory.current must not increase
+ * beyond its baseline after the migration.
+ *
+ * Test 2 - New allocations charged to destination cgroup:
+ * After migration the worker allocates an additional chunk of memory.
+ * This new allocation must be charged to group_b, so
+ * group_b/memory.current must increase from the post-migration
+ * baseline.
+ */
+
+#define _GNU_SOURCE
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include "tst_test.h"
+#include "memcontrol_common.h"
+
+#define ALLOC_SIZE MB(60)
+#define ALLOC_SIZE2 MB(20)
+
+enum checkpoints {
+ WORKER_ALLOC_DONE,
+ WORKER_RESUME,
+ WORKER_ALLOC2_DONE,
+ WORKER_EXIT,
+};
+
+static struct tst_cg_group *group_a;
+static struct tst_cg_group *group_b;
+static bool worker_resumed;
+
+static void worker(void)
+{
+ char *buf1, *buf2;
+
+ SAFE_CG_PRINTF(group_a, "cgroup.procs", "%d", getpid());
+
+ buf1 = SAFE_MMAP(NULL, ALLOC_SIZE, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0);
+
+ TST_CHECKPOINT_WAKE(WORKER_ALLOC_DONE);
+
+ TST_CHECKPOINT_WAIT(WORKER_RESUME);
+
+ buf2 = SAFE_MMAP(NULL, ALLOC_SIZE2, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0);
+
+ TST_CHECKPOINT_WAKE(WORKER_ALLOC2_DONE);
+
+ TST_CHECKPOINT_WAIT(WORKER_EXIT);
+
+ SAFE_MUNMAP(buf1, ALLOC_SIZE);
+ SAFE_MUNMAP(buf2, ALLOC_SIZE2);
+}
+
+static void test_memcg_task_migration(void)
+{
+ pid_t pid;
+ long baseline_b, after_migrate, after_alloc2, current_a;
+ worker_resumed = false;
+
+ group_a = tst_cg_group_mk(tst_cg, "group_a");
+ group_b = tst_cg_group_mk(tst_cg, "group_b");
+
+ SAFE_CG_SCANF(group_b, "memory.current", "%ld", &baseline_b);
+ tst_res(TINFO, "group_b baseline memory.current=%ld", baseline_b);
+
+ pid = SAFE_FORK();
+ if (!pid) {
+ worker();
+ exit(0);
+ }
+
+ TST_CHECKPOINT_WAIT(WORKER_ALLOC_DONE);
+
+ SAFE_CG_SCANF(group_a, "memory.current", "%ld", ¤t_a);
+ tst_res(TINFO, "group_a memory.current=%ld after alloc", current_a);
+ if (current_a < (long)ALLOC_SIZE) {
+ tst_res(TFAIL,
+ "group_a memory.current (%ld) < ALLOC_SIZE (%ld)",
+ current_a, (long)ALLOC_SIZE);
+ goto done;
+ }
+ tst_res(TPASS,
+ "group_a memory.current (%ld) >= ALLOC_SIZE (%ld)",
+ current_a, (long)ALLOC_SIZE);
+
+ SAFE_CG_PRINTF(group_b, "cgroup.procs", "%d", pid);
+ tst_res(TINFO, "Migrated worker PID %d to group_b", pid);
+
+ SAFE_CG_SCANF(group_b, "memory.current", "%ld", &after_migrate);
+ tst_res(TINFO,
+ "group_b memory.current=%ld after migration (baseline=%ld)",
+ after_migrate, baseline_b);
+
+ TST_EXP_EXPR(after_migrate <= baseline_b + (long)MB(4),
+ "group_b memory.current (%ld) not increased after migration (baseline=%ld)",
+ after_migrate, baseline_b);
+
+ TST_CHECKPOINT_WAKE(WORKER_RESUME);
+ worker_resumed = true;
+ TST_CHECKPOINT_WAIT(WORKER_ALLOC2_DONE);
+
+ SAFE_CG_SCANF(group_b, "memory.current", "%ld", &after_alloc2);
+ tst_res(TINFO, "group_b memory.current=%ld after second alloc (baseline=%ld)",
+ after_alloc2, after_migrate);
+
+ TST_EXP_EXPR(after_alloc2 >= after_migrate + (long)ALLOC_SIZE2,
+ "group_b memory.current (%ld) increased by >= ALLOC_SIZE2 (%ld)",
+ after_alloc2, (long)ALLOC_SIZE2);
+
+done:
+ if (!worker_resumed) {
+ TST_CHECKPOINT_WAKE(WORKER_RESUME);
+ TST_CHECKPOINT_WAIT(WORKER_ALLOC2_DONE);
+ }
+ TST_CHECKPOINT_WAKE(WORKER_EXIT);
+ tst_reap_children();
+
+ group_a = tst_cg_group_rm(group_a);
+ group_b = tst_cg_group_rm(group_b);
+}
+
+static void cleanup(void)
+{
+ if (group_a)
+ group_a = tst_cg_group_rm(group_a);
+ if (group_b)
+ group_b = tst_cg_group_rm(group_b);
+}
+
+static struct tst_test test = {
+ .test_all = test_memcg_task_migration,
+ .cleanup = cleanup,
+ .forks_child = 1,
+ .needs_root = 1,
+ .needs_checkpoints = 1,
+ .needs_cgroup_ver = TST_CG_V2,
+ .needs_cgroup_ctrls = (const char *const []){ "memory", NULL },
+};
--
2.55.0
More information about the ltp
mailing list