summaryrefslogtreecommitdiff
path: root/tools/testing/selftests
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-07-26 23:42:07 +0100
committerMark Brown <broonie@kernel.org>2026-07-26 23:42:07 +0100
commita989078cd41b6e1e9be90b047e4881d6f868153b (patch)
tree276933e5883b9c57d0e34a5525fdca59aa6c707d /tools/testing/selftests
parent6998538ebe42a24094359b820b535695fdf3660a (diff)
parent0ccf15cbb54ce4413e894fc9ed91abecc4f1017c (diff)
downloadlinux-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.h1
-rw-r--r--tools/testing/selftests/cgroup/test_core.c1
-rw-r--r--tools/testing/selftests/cgroup/test_cpu.c43
-rw-r--r--tools/testing/selftests/cgroup/test_cpuset.c247
-rwxr-xr-xtools/testing/selftests/cgroup/test_cpuset_prs.sh11
-rw-r--r--tools/testing/selftests/cgroup/test_hugetlb_memcg.c10
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();
}