diff options
| author | Mark Brown <broonie@kernel.org> | 2026-07-26 23:42:07 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-07-26 23:42:07 +0100 |
| commit | a989078cd41b6e1e9be90b047e4881d6f868153b (patch) | |
| tree | 276933e5883b9c57d0e34a5525fdca59aa6c707d /tools/testing/selftests | |
| parent | 6998538ebe42a24094359b820b535695fdf3660a (diff) | |
| parent | 0ccf15cbb54ce4413e894fc9ed91abecc4f1017c (diff) | |
| download | linux-next-a989078cd41b6e1e9be90b047e4881d6f868153b.tar.gz linux-next-a989078cd41b6e1e9be90b047e4881d6f868153b.zip | |
Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup.git
Diffstat (limited to 'tools/testing/selftests')
| -rw-r--r-- | tools/testing/selftests/cgroup/lib/include/cgroup_util.h | 1 | ||||
| -rw-r--r-- | tools/testing/selftests/cgroup/test_core.c | 1 | ||||
| -rw-r--r-- | tools/testing/selftests/cgroup/test_cpu.c | 43 | ||||
| -rw-r--r-- | tools/testing/selftests/cgroup/test_cpuset.c | 247 | ||||
| -rwxr-xr-x | tools/testing/selftests/cgroup/test_cpuset_prs.sh | 11 | ||||
| -rw-r--r-- | tools/testing/selftests/cgroup/test_hugetlb_memcg.c | 10 |
6 files changed, 300 insertions, 13 deletions
diff --git a/tools/testing/selftests/cgroup/lib/include/cgroup_util.h b/tools/testing/selftests/cgroup/lib/include/cgroup_util.h index febc1723d090..8ebb2b4d4ec0 100644 --- a/tools/testing/selftests/cgroup/lib/include/cgroup_util.h +++ b/tools/testing/selftests/cgroup/lib/include/cgroup_util.h @@ -8,6 +8,7 @@ #define MB(x) (x << 20) +#define NSEC_PER_USEC 1000L #define USEC_PER_SEC 1000000L #define NSEC_PER_SEC 1000000000L diff --git a/tools/testing/selftests/cgroup/test_core.c b/tools/testing/selftests/cgroup/test_core.c index 88ca832d4fc1..8e3f9b391e44 100644 --- a/tools/testing/selftests/cgroup/test_core.c +++ b/tools/testing/selftests/cgroup/test_core.c @@ -427,7 +427,6 @@ static int test_cgcore_no_internal_process_constraint_on_threads(const char *roo cleanup: cg_enter_current(root); - cg_enter_current(root); if (child) cg_destroy(child); if (parent) diff --git a/tools/testing/selftests/cgroup/test_cpu.c b/tools/testing/selftests/cgroup/test_cpu.c index 7a40d76b9548..a5eccfcabef5 100644 --- a/tools/testing/selftests/cgroup/test_cpu.c +++ b/tools/testing/selftests/cgroup/test_cpu.c @@ -640,21 +640,49 @@ test_cpucg_nested_weight_underprovisioned(const char *root) } /* + * Best effort attempt to get the kernel's HZ value from the config. + * Return the HZ value if found otherwise return 1000 (the default) to + * indicate failure. + */ +static long +get_config_hz(void) +{ + long hz = 1000; + FILE *f; + char cmd[256] = "zcat /proc/config.gz 2>/dev/null | grep '^CONFIG_HZ='"; + + f = popen(cmd, "r"); + + if (!f) + return hz; + + if (fscanf(f, "CONFIG_HZ=%ld", &hz) == EOF) + goto out; + +out: + pclose(f); + return hz; +} + +/* * This test creates a cgroup with some maximum value within a period, and * verifies that a process in the cgroup is not overscheduled. */ static int test_cpucg_max(const char *root) { int ret = KSFT_FAIL; + long hz = get_config_hz(); long quota_usec = 1000; long default_period_usec = 100000; /* cpu.max's default period */ long duration_seconds = 1; - long duration_usec = duration_seconds * USEC_PER_SEC; + long duration_usec; long usage_usec, n_periods, remainder_usec, expected_usage_usec; char *cpucg; char quota_buf[32]; + duration_usec = duration_seconds * USEC_PER_SEC * 1000 / hz; + snprintf(quota_buf, sizeof(quota_buf), "%ld", quota_usec); cpucg = cg_name(root, "cpucg_test"); @@ -670,8 +698,8 @@ static int test_cpucg_max(const char *root) struct cpu_hog_func_param param = { .nprocs = 1, .ts = { - .tv_sec = duration_seconds, - .tv_nsec = 0, + .tv_sec = duration_usec / USEC_PER_SEC, + .tv_nsec = duration_usec % USEC_PER_SEC * NSEC_PER_USEC, }, .clock_type = CPU_HOG_CLOCK_WALL, }; @@ -710,15 +738,18 @@ cleanup: static int test_cpucg_max_nested(const char *root) { int ret = KSFT_FAIL; + long hz = get_config_hz(); long quota_usec = 1000; long default_period_usec = 100000; /* cpu.max's default period */ long duration_seconds = 1; - long duration_usec = duration_seconds * USEC_PER_SEC; + long duration_usec; long usage_usec, n_periods, remainder_usec, expected_usage_usec; char *parent, *child; char quota_buf[32]; + duration_usec = duration_seconds * USEC_PER_SEC * 1000 / hz; + snprintf(quota_buf, sizeof(quota_buf), "%ld", quota_usec); parent = cg_name(root, "cpucg_parent"); @@ -741,8 +772,8 @@ static int test_cpucg_max_nested(const char *root) struct cpu_hog_func_param param = { .nprocs = 1, .ts = { - .tv_sec = duration_seconds, - .tv_nsec = 0, + .tv_sec = duration_usec / USEC_PER_SEC, + .tv_nsec = duration_usec % USEC_PER_SEC * NSEC_PER_USEC, }, .clock_type = CPU_HOG_CLOCK_WALL, }; diff --git a/tools/testing/selftests/cgroup/test_cpuset.c b/tools/testing/selftests/cgroup/test_cpuset.c index c5cf8b56ceb8..8c2d4d4ef1fc 100644 --- a/tools/testing/selftests/cgroup/test_cpuset.c +++ b/tools/testing/selftests/cgroup/test_cpuset.c @@ -1,7 +1,13 @@ // SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include <assert.h> #include <linux/limits.h> +#include <pthread.h> +#include <sched.h> #include <signal.h> +#include <sys/syscall.h> +#include <unistd.h> #include "kselftest.h" #include "cgroup_util.h" @@ -232,6 +238,246 @@ cleanup: return ret; } +static int get_cpu_affinity(cpu_set_t *mask) +{ + CPU_ZERO(mask); + return sched_getaffinity(0, sizeof(*mask), mask); +} + +static int cpu_set_equal(cpu_set_t *dst, unsigned long mask) +{ + cpu_set_t expected; + + CPU_ZERO(&expected); + assert(sizeof(mask) < CPU_SETSIZE); + + for (int cpu = 0; cpu < sizeof(mask) * 8; ++cpu) + if ((1UL << cpu) & mask) + CPU_SET(cpu, &expected); + + return CPU_EQUAL(&expected, dst); +} + +enum test_phase { + AFFINITY_SETUP, + AFFINITY_CONTROLLER_DISABLED, + AFFINITY_COMPLETE, + AFFINITY_ERROR +}; + +struct thread_args { + const char *cgroup; + cpu_set_t *affinity_before; + cpu_set_t *affinity_after; + int affinity_before_ready; +}; + +static pthread_mutex_t test_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t test_cond = PTHREAD_COND_INITIALIZER; +static enum test_phase test_phase; + +static void *affinity_thread_fn(void *arg) +{ + struct thread_args *args = (struct thread_args *)arg; + + if (cg_enter_current_thread(args->cgroup)) + goto fail; + + if (get_cpu_affinity(args->affinity_before) != 0) + goto fail; + + pthread_mutex_lock(&test_mutex); + args->affinity_before_ready = 1; + pthread_cond_broadcast(&test_cond); + + while (test_phase < AFFINITY_CONTROLLER_DISABLED) + pthread_cond_wait(&test_cond, &test_mutex); + pthread_mutex_unlock(&test_mutex); + + if (get_cpu_affinity(args->affinity_after) != 0) + goto fail; + + + return NULL; + +fail: + pthread_mutex_lock(&test_mutex); + test_phase = AFFINITY_ERROR; + pthread_cond_broadcast(&test_cond); + pthread_mutex_unlock(&test_mutex); + return NULL; +} + +/* + * Test that disabling cpuset controller properly updates thread affinity. + * + * This test exposes a bug in cpuset_attach() where threads in child cgroups + * don't get their affinity updated when the cpuset controller is disabled. + * + * Setup: + * - Create parent cgroup with cpuset.cpus=0-1 + * - Create child A with cpuset.cpus=0-1 + * - Create child B with cpuset.cpus=1 + * - Place multithreaded process: group leader + thread_a in A, thread_b in B + * - Disable cpuset controller on parent + * + * Expected: thread_b's affinity should expand from {1} to {0-1} + * Buggy: thread_b's affinity remains {1} + */ +static int test_cpuset_affinity_on_controller_disable(const char *root) +{ + char *parent = NULL, *child_a = NULL, *child_b = NULL; + pthread_t thread_a, thread_b; + int thread_a_created = 0, thread_b_created = 0; + cpu_set_t affinity_a_before, affinity_a_after; + cpu_set_t affinity_b_before, affinity_b_after; + int ret = KSFT_FAIL; + + parent = cg_name(root, "cpuset_affinity_test"); + if (!parent) + goto cleanup; + if (cg_create(parent)) + goto cleanup; + if (cg_write(parent, "cgroup.type", "threaded")) + goto cleanup; + + child_a = cg_name(parent, "A"); + if (!child_a) + goto cleanup; + if (cg_create(child_a)) + goto cleanup; + if (cg_write(child_a, "cgroup.type", "threaded")) + goto cleanup; + + child_b = cg_name(parent, "B"); + if (!child_b) + goto cleanup; + if (cg_create(child_b)) + goto cleanup; + if (cg_write(child_b, "cgroup.type", "threaded")) + goto cleanup; + + /* Now enable cpuset controller in parent */ + if (cg_write(parent, "cgroup.subtree_control", "+cpuset")) + goto skip; + + /* + * Set CPU affinity constraints + * Skip the test if the setting of "cpuset.cpus" fails as the test + * system may not have CPU 1. + */ + if (cg_write(parent, "cpuset.cpus", "0-1")) + goto skip; + if (cg_write(child_a, "cpuset.cpus", "0-1")) + goto skip; + if (cg_write(child_b, "cpuset.cpus", "1")) + goto skip; + + /* Move group leader (main thread) to child A */ + if (cg_enter_current(child_a)) + goto cleanup; + + /* Create threads - they will move themselves to their respective cgroups */ + test_phase = AFFINITY_SETUP; + + struct thread_args args_a = { + .cgroup = child_a, + .affinity_before = &affinity_a_before, + .affinity_after = &affinity_a_after, + .affinity_before_ready = 0, + }; + if (pthread_create(&thread_a, NULL, affinity_thread_fn, &args_a)) + goto cleanup; + thread_a_created = 1; + + struct thread_args args_b = { + .cgroup = child_b, + .affinity_before = &affinity_b_before, + .affinity_after = &affinity_b_after, + .affinity_before_ready = 0, + }; + if (pthread_create(&thread_b, NULL, affinity_thread_fn, &args_b)) + goto cleanup_threads; + thread_b_created = 1; + + pthread_mutex_lock(&test_mutex); + while ((test_phase < AFFINITY_ERROR) && + (args_a.affinity_before_ready + args_b.affinity_before_ready < 2)) + pthread_cond_wait(&test_cond, &test_mutex); + + /* If a thread failed during setup, bail out */ + if (test_phase == AFFINITY_ERROR) { + pthread_mutex_unlock(&test_mutex); + goto cleanup_threads; + } + pthread_mutex_unlock(&test_mutex); + + if (!cpu_set_equal(&affinity_a_before, 0x3)) { + ksft_print_msg("FAIL: thread_a initial affinity incorrect\n"); + goto cleanup_threads; + } + + if (!cpu_set_equal(&affinity_b_before, 0x2)) { + ksft_print_msg("FAIL: thread_b initial affinity incorrect\n"); + goto cleanup_threads; + } + + /* Disable cpuset controller - this should trigger affinity update */ + if (cg_write(parent, "cgroup.subtree_control", "-cpuset")) + goto cleanup_threads; + + /* Signal threads to save their final affinity and exit */ + pthread_mutex_lock(&test_mutex); + test_phase = AFFINITY_CONTROLLER_DISABLED; + pthread_cond_broadcast(&test_cond); + pthread_mutex_unlock(&test_mutex); + + pthread_join(thread_a, NULL); + pthread_join(thread_b, NULL); + + /* Verify thread affinities AFTER disabling controller */ + if (!cpu_set_equal(&affinity_a_after, 0x3)) { + ksft_print_msg("FAIL: thread_a final affinity incorrect\n"); + goto cleanup; + } + + if (!cpu_set_equal(&affinity_b_after, 0x3)) { + ksft_print_msg("FAIL: thread_b affinity did not expand to {0-1}\n"); + goto cleanup; + } + + ret = KSFT_PASS; + goto cleanup; + +skip: + ret = KSFT_SKIP; + goto cleanup; + +cleanup_threads: + pthread_mutex_lock(&test_mutex); + test_phase = AFFINITY_COMPLETE; + pthread_cond_broadcast(&test_cond); + pthread_mutex_unlock(&test_mutex); + + if (thread_a_created) + pthread_join(thread_a, NULL); + if (thread_b_created) + pthread_join(thread_b, NULL); + +cleanup: + /* Move back to root before cleanup */ + cg_enter_current(root); + + cg_destroy(child_b); + free(child_b); + cg_destroy(child_a); + free(child_a); + cg_destroy(parent); + free(parent); + + return ret; +} + #define T(x) { x, #x } struct cpuset_test { @@ -241,6 +487,7 @@ struct cpuset_test { T(test_cpuset_perms_object_allow), T(test_cpuset_perms_object_deny), T(test_cpuset_perms_subtree), + T(test_cpuset_affinity_on_controller_disable), }; #undef T diff --git a/tools/testing/selftests/cgroup/test_cpuset_prs.sh b/tools/testing/selftests/cgroup/test_cpuset_prs.sh index 0d41aa0d343d..ca9bc38fdb95 100755 --- a/tools/testing/selftests/cgroup/test_cpuset_prs.sh +++ b/tools/testing/selftests/cgroup/test_cpuset_prs.sh @@ -495,13 +495,20 @@ REMOTE_TEST_MATRIX=( # Narrowing cpuset.cpus to previously sibling-excluded CPUs should # not return CPUs that were never actually owned. " C1-4:P1 . C1-2:P1 C1-3:P2 . . \ - . . . C3 . . p1:4|c11:1-2|c12:3 \ + . . . C3 . . p1:4|c11:1-2|c12:3 \ p1:P1|c11:P1|c12:P2 3" # Expanding cpuset.cpus to include a previously sibling-excluded CPU # after the sibling has become a member should correctly request it. " C1-4:P1 . C1-2:P1 C1-3:P2 . . \ - . . P0 C2-3 . . p1:1,4|c11:1|c12:2-3 \ + . . P0 C2-3 . . p1:1,4|c11:1|c12:2-3 \ p1:P1|c11:P0|c12:P2 2-3" + # Cpusets with empty cpuset.cpus should inherit parent's effective_cpus + " C1-4:P1 C5-6 C1-2 . C5 . \ + . P1 P1 . . . p1:3-4|p2:5-6|c11:1-2|c12:3-4|c21:5|c22:5-6 \ + p1:P1|p2:P1|c11:P1" + " C1-4:P1 C5-6 C1-2 . C5 . \ + . P1 P1 . O5=0 . p1:3-4|p2:6|c11:1-2|c12:3-4|c21:6|c22:6 \ + p1:P1|p2:P1|c11:P1" ) # diff --git a/tools/testing/selftests/cgroup/test_hugetlb_memcg.c b/tools/testing/selftests/cgroup/test_hugetlb_memcg.c index b627d84358b1..8c5aced813b6 100644 --- a/tools/testing/selftests/cgroup/test_hugetlb_memcg.c +++ b/tools/testing/selftests/cgroup/test_hugetlb_memcg.c @@ -199,7 +199,10 @@ out: int main(int argc, char **argv) { char root[PATH_MAX]; - int ret = EXIT_SUCCESS, has_memory_hugetlb_acc; + int has_memory_hugetlb_acc; + + ksft_print_header(); + ksft_set_plan(1); has_memory_hugetlb_acc = proc_mount_contains("memory_hugetlb_accounting"); if (has_memory_hugetlb_acc < 0) @@ -211,7 +214,7 @@ int main(int argc, char **argv) if (get_hugepage_size() != 2048) { ksft_print_msg("test_hugetlb_memcg requires 2MB hugepages\n"); ksft_test_result_skip("test_hugetlb_memcg\n"); - return ret; + ksft_finished(); } if (cg_find_unified_root(root, sizeof(root), NULL)) @@ -233,10 +236,9 @@ int main(int argc, char **argv) ksft_test_result_skip("test_hugetlb_memcg\n"); break; default: - ret = EXIT_FAILURE; ksft_test_result_fail("test_hugetlb_memcg\n"); break; } - return ret; + ksft_finished(); } |
