[LTP] [PATCH v2] memcg/memcontrol05: add cgroup v2 task migration charge accounting test
Pavithra
pavrampu@linux.ibm.com
Wed Aug 12 18:16:13 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>
---
v1 -> v2:
- Replace MAP_POPULATE with explicit page-touching via touch_pages()
- Add .min_mem_avail = 80 (MB) to skip test under low memory
- Add blank line after declarations in test_memcg_task_migration()
- Use double backticks for identifiers and paths in RST block
- Document why .needs_root = 1 is required in test description
Link to v1: https://lore.kernel.org/ltp/20260804095925.495813-1-pavrampu@linux.ibm.com/
---
runtest/controllers | 1 +
testcases/kernel/controllers/memcg/.gitignore | 1 +
.../kernel/controllers/memcg/memcontrol05.c | 175 ++++++++++++++++++
3 files changed, 177 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..4cb727ffa
--- /dev/null
+++ b/testcases/kernel/controllers/memcg/memcontrol05.c
@@ -0,0 +1,175 @@
+// 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.
+ * Root is required to create cgroup subdirectories and to write a PID
+ * into ``cgroup.procs`` to migrate the worker between cgroups.
+ *
+ * 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)
+#define MIN_MEM_AVAIL 80
+
+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 touch_pages(char *buf, size_t size)
+{
+ size_t i;
+
+ for (i = 0; i < size; i += getpagesize())
+ buf[i] = 1;
+}
+
+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, -1, 0);
+ touch_pages(buf1, ALLOC_SIZE);
+
+ 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, -1, 0);
+ touch_pages(buf2, ALLOC_SIZE2);
+
+ 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 },
+ .min_mem_avail = MIN_MEM_AVAIL,
+};
--
2.55.0
More information about the ltp
mailing list