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