The commit is pushed to "branch-rh10-6.12.0-211.16.1.12.x.vz10-ovz" and will 
appear at [email protected]:openvz/vzkernel.git
after rh10-6.12.0-211.16.1.12.4.vz10
------>
commit 4fc916d7c02abafe542ac1c0293df9efef63f0cb
Author: Pavel Tikhomirov <[email protected]>
Date:   Tue Jun 16 13:54:26 2026 +0200

    kselftest/cgroup/freezer: test freezing cgroups with kernel threads
    
    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 <[email protected]>
---
 tools/testing/selftests/cgroup/Makefile            |   1 +
 tools/testing/selftests/cgroup/config              |   2 +
 tools/testing/selftests/cgroup/test_freezer.c      | 122 +++++++++++++++++++++
 .../testing/selftests/cgroup/test_modules/Makefile |  16 +++
 .../cgroup/test_modules/cg_freezer_kthread.c       |  73 ++++++++++++
 5 files changed, 214 insertions(+)

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 <[email protected]>");
+MODULE_DESCRIPTION("cgroup freezer test: spawn a spare kernel thread");
_______________________________________________
Devel mailing list
[email protected]
https://lists.openvz.org/mailman/listinfo/devel

Reply via email to