[Devel] [PATCH VZ10 1/2] kselftest/cgroup/freezer: test freezing cgroups with kernel threads
Pavel Tikhomirov
ptikhomirov at virtuozzo.com
Tue Jun 16 14:54:26 MSK 2026
Add a selftest for the "cgroup-v2/freezer: allow freezing with kthreads"
change: a cgroup whose only task is a kernel thread must still be
freezable, since kthreads are ignored by the freezer and offset against
the frozen task count.
The test uses a helper module (cg_freezer_kthread) that spawns a spare
kernel thread and exports its pid via /proc, so the test can move it
into a freezer cgroup and check that the cgroup reports frozen. This
also introduces the test_modules/ build directory and the module
load/unload helpers shared with later freezer tests.
Note: requires kernel-devel package to build the helper modules.
https://virtuozzo.atlassian.net/browse/VSTOR-119676
Feature: cgroup/freeze: improvements
Signed-off-by: Pavel Tikhomirov <ptikhomirov at virtuozzo.com>
---
tools/testing/selftests/cgroup/Makefile | 1 +
tools/testing/selftests/cgroup/config | 2 +
tools/testing/selftests/cgroup/test_freezer.c | 122 ++++++++++++++++++
.../selftests/cgroup/test_modules/Makefile | 16 +++
.../cgroup/test_modules/cg_freezer_kthread.c | 73 +++++++++++
5 files changed, 214 insertions(+)
create mode 100644 tools/testing/selftests/cgroup/test_modules/Makefile
create mode 100644 tools/testing/selftests/cgroup/test_modules/cg_freezer_kthread.c
diff --git a/tools/testing/selftests/cgroup/Makefile b/tools/testing/selftests/cgroup/Makefile
index 2b8395feccd4c..a0b52fb9bb341 100644
--- a/tools/testing/selftests/cgroup/Makefile
+++ b/tools/testing/selftests/cgroup/Makefile
@@ -6,6 +6,7 @@ all: ${HELPER_PROGS}
TEST_FILES := with_stress.sh
TEST_PROGS := test_stress.sh test_cpuset_prs.sh test_cpuset_v1_hp.sh
TEST_GEN_FILES := wait_inotify
+TEST_GEN_MODS_DIR := test_modules
# Keep the lists lexicographically sorted
TEST_GEN_PROGS = test_core
TEST_GEN_PROGS += test_cpu
diff --git a/tools/testing/selftests/cgroup/config b/tools/testing/selftests/cgroup/config
index ac6625ba19945..c87a6df7edb53 100644
--- a/tools/testing/selftests/cgroup/config
+++ b/tools/testing/selftests/cgroup/config
@@ -8,3 +8,5 @@ CONFIG_NUMA=y
CONFIG_MEMFD_CREATE=y
CONFIG_TRANSPARENT_HUGEPAGE=y
CONFIG_CACHESTAT_SYSCALL=y
+CONFIG_MODULES=y
+CONFIG_MODULE_UNLOAD=y
diff --git a/tools/testing/selftests/cgroup/test_freezer.c b/tools/testing/selftests/cgroup/test_freezer.c
index 8730645d363a7..0adc5b6c4ac78 100644
--- a/tools/testing/selftests/cgroup/test_freezer.c
+++ b/tools/testing/selftests/cgroup/test_freezer.c
@@ -21,6 +21,61 @@
#define debug(args...)
#endif
+/*
+ * Directory containing the test binary. The helper modules live in a
+ * test_modules/ subdirectory next to it (rsync'd there by TEST_GEN_MODS_DIR),
+ * so resolve them relative to the binary rather than the current working
+ * directory - the test may be run from anywhere.
+ */
+static const char *exe_dir(void)
+{
+ static char dir[PATH_MAX];
+ static int done;
+ char *slash;
+ ssize_t n;
+
+ if (done)
+ return dir;
+ done = 1;
+
+ n = readlink("/proc/self/exe", dir, sizeof(dir) - 1);
+ if (n < 0) {
+ strcpy(dir, ".");
+ return dir;
+ }
+ dir[n] = '\0';
+
+ slash = strrchr(dir, '/');
+ if (slash)
+ *slash = '\0';
+ else
+ strcpy(dir, ".");
+
+ return dir;
+}
+
+/*
+ * Load/unload a helper module from the test_modules/ directory next to the
+ * test binary. Returns 0 on success.
+ */
+static int mod_load(const char *mod)
+{
+ char cmd[2 * PATH_MAX];
+
+ snprintf(cmd, sizeof(cmd), "insmod %s/test_modules/%s.ko 2>/dev/null",
+ exe_dir(), mod);
+ return system(cmd) ? -1 : 0;
+}
+
+static void mod_unload(const char *mod)
+{
+ char cmd[PATH_MAX];
+
+ snprintf(cmd, sizeof(cmd), "rmmod %s 2>/dev/null", mod);
+ if (system(cmd))
+ debug("Failed to unload module %s\n", mod);
+}
+
/*
* Check if the cgroup is frozen by looking at the cgroup.events::frozen value.
*/
@@ -804,6 +859,72 @@ static int test_cgfreezer_vfork(const char *root)
return ret;
}
+/*
+ * Read the pid of the spare kernel thread spawned by cg_freezer_kthread.
+ */
+static int read_kthread_pid(void)
+{
+ int pid = -1;
+ FILE *f;
+
+ f = fopen("/proc/cg_freezer_kthread", "r");
+ if (!f)
+ return -1;
+ if (fscanf(f, "%d", &pid) != 1)
+ pid = -1;
+ fclose(f);
+ return pid;
+}
+
+/*
+ * Test that a cgroup whose only task is a kernel thread can be frozen.
+ * Kernel threads are ignored by the freezer and offset against the frozen
+ * task count, so the cgroup must report frozen in short time.
+ */
+static int test_cgfreezer_kthread(const char *root)
+{
+ int ret = KSFT_FAIL;
+ char *cgroup = NULL;
+ int kpid;
+
+ if (mod_load("cg_freezer_kthread"))
+ return KSFT_SKIP;
+
+ kpid = read_kthread_pid();
+ if (kpid <= 0)
+ goto cleanup;
+
+ cgroup = cg_name(root, "cg_test_kthread");
+ if (!cgroup)
+ goto cleanup;
+
+ if (cg_create(cgroup))
+ goto cleanup;
+
+ /* Move the spare kernel thread into the freezer cgroup. */
+ if (cg_enter(cgroup, kpid))
+ goto cleanup;
+
+ /*
+ * Freezing must succeed quickly even though the only task is a
+ * kernel thread.
+ */
+ if (cg_freeze_wait(cgroup, true))
+ goto cleanup;
+
+ ret = KSFT_PASS;
+
+cleanup:
+ if (cgroup)
+ cg_freeze_nowait(cgroup, false);
+ /* Unloading stops the kthread, which then leaves the cgroup. */
+ mod_unload("cg_freezer_kthread");
+ if (cgroup)
+ cg_destroy(cgroup);
+ free(cgroup);
+ return ret;
+}
+
#define T(x) { x, #x }
struct cgfreezer_test {
int (*fn)(const char *root);
@@ -819,6 +940,7 @@ struct cgfreezer_test {
T(test_cgfreezer_stopped),
T(test_cgfreezer_ptraced),
T(test_cgfreezer_vfork),
+ T(test_cgfreezer_kthread),
};
#undef T
diff --git a/tools/testing/selftests/cgroup/test_modules/Makefile b/tools/testing/selftests/cgroup/test_modules/Makefile
new file mode 100644
index 0000000000000..dd401a4345d4b
--- /dev/null
+++ b/tools/testing/selftests/cgroup/test_modules/Makefile
@@ -0,0 +1,16 @@
+TESTMODS_DIR := $(realpath $(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
+KDIR ?= /lib/modules/$(shell uname -r)/build
+
+obj-m += cg_freezer_kthread.o
+
+# Ensure that KDIR exists, otherwise skip the compilation
+modules:
+ifneq ("$(wildcard $(KDIR))", "")
+ $(Q)$(MAKE) -C $(KDIR) modules KBUILD_EXTMOD=$(TESTMODS_DIR)
+endif
+
+# Ensure that KDIR exists, otherwise skip the clean target
+clean:
+ifneq ("$(wildcard $(KDIR))", "")
+ $(Q)$(MAKE) -C $(KDIR) clean KBUILD_EXTMOD=$(TESTMODS_DIR)
+endif
diff --git a/tools/testing/selftests/cgroup/test_modules/cg_freezer_kthread.c b/tools/testing/selftests/cgroup/test_modules/cg_freezer_kthread.c
new file mode 100644
index 0000000000000..577aa8e0d3735
--- /dev/null
+++ b/tools/testing/selftests/cgroup/test_modules/cg_freezer_kthread.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Helper module for the cgroup-v2 freezer selftest
+ * "test_cgfreezer_kthread".
+ *
+ * It spawns one spare kernel thread that just sleeps until the module is
+ * unloaded, and exposes its (init pid namespace) pid via
+ * /proc/cg_freezer_kthread. The selftest reads that pid, moves the kthread
+ * into a freezer cgroup and checks that the cgroup can still be frozen --
+ * kernel threads are ignored by the freezer and offset against the frozen
+ * count, so a cgroup whose only task is a kthread must report frozen.
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/kthread.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/pid.h>
+#include <linux/pid_namespace.h>
+
+static struct task_struct *spare_kthread;
+
+static int cg_freezer_kthread_fn(void *data)
+{
+ while (!kthread_should_stop()) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (kthread_should_stop()) {
+ set_current_state(TASK_RUNNING);
+ break;
+ }
+ schedule_timeout(HZ);
+ }
+ return 0;
+}
+
+static int cg_freezer_kthread_show(struct seq_file *m, void *v)
+{
+ seq_printf(m, "%d\n", task_pid_nr_ns(spare_kthread, &init_pid_ns));
+ return 0;
+}
+
+static int __init cg_freezer_kthread_init(void)
+{
+ spare_kthread = kthread_run(cg_freezer_kthread_fn, NULL,
+ "cg_freezer_kthr");
+ if (IS_ERR(spare_kthread))
+ return PTR_ERR(spare_kthread);
+
+ if (!proc_create_single("cg_freezer_kthread", 0444, NULL,
+ cg_freezer_kthread_show)) {
+ kthread_stop(spare_kthread);
+ return -ENOMEM;
+ }
+
+ pr_info("spare kthread pid %d\n",
+ task_pid_nr_ns(spare_kthread, &init_pid_ns));
+ return 0;
+}
+
+static void __exit cg_freezer_kthread_exit(void)
+{
+ remove_proc_entry("cg_freezer_kthread", NULL);
+ kthread_stop(spare_kthread);
+}
+
+module_init(cg_freezer_kthread_init);
+module_exit(cg_freezer_kthread_exit);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Pavel Tikhomirov <ptikhomirov at virtuozzo.com>");
+MODULE_DESCRIPTION("cgroup freezer test: spawn a spare kernel thread");
--
2.54.0
More information about the Devel
mailing list