Implement a new selftest to verify sub-sched (nested scheduler) attachment and detachment. This test was missing despite sub-sched being a complex feature that has seen multiple bug fixes (cgroup double-put, uninitialized return value, abort path issues).
Sub-sched allows a child BPF scheduler to manage tasks within a specific cgroup subtree, coordinating with the parent scheduler via sub_attach/sub_detach operations. Test structure: - Parent BPF scheduler: Implements sub_attach/sub_detach ops; sub_attach returns s32 as required by the kernel ABI - Child BPF scheduler: Minimal dispatch logic, configured with sub_cgroup_id pointing to a test cgroup - Test driver: Creates a cgroup under /sys/fs/cgroup/ (cgroupfs), obtains the cgroup ID via stat().st_ino (which equals the kernfs node ID on cgroupfs), loads both schedulers, attaches child to the cgroup, verifies clean detachment Note: The cgroup must be created under /sys/fs/cgroup/ rather than /tmp/, because cgroup_get_from_id() uses kernfs_find_and_get_node_by_id() in the cgroupfs kernfs root. A tmpfs inode is not a valid cgroup ID. Signed-off-by: zhidao su <[email protected]> --- tools/testing/selftests/sched_ext/Makefile | 1 + tools/testing/selftests/sched_ext/sub_sched.c | 200 ++++++++++++++++++ .../selftests/sched_ext/sub_sched_child.bpf.c | 31 +++ .../sched_ext/sub_sched_parent.bpf.c | 35 +++ 4 files changed, 267 insertions(+) create mode 100644 tools/testing/selftests/sched_ext/sub_sched.c create mode 100644 tools/testing/selftests/sched_ext/sub_sched_child.bpf.c create mode 100644 tools/testing/selftests/sched_ext/sub_sched_parent.bpf.c diff --git a/tools/testing/selftests/sched_ext/Makefile b/tools/testing/selftests/sched_ext/Makefile index 84e4f69b8833..211eef9443a9 100644 --- a/tools/testing/selftests/sched_ext/Makefile +++ b/tools/testing/selftests/sched_ext/Makefile @@ -190,6 +190,7 @@ auto-test-targets := \ select_cpu_dispatch_dbl_dsp \ select_cpu_vtime \ rt_stall \ + sub_sched \ test_example \ total_bw \ diff --git a/tools/testing/selftests/sched_ext/sub_sched.c b/tools/testing/selftests/sched_ext/sub_sched.c new file mode 100644 index 000000000000..4fbd4a60ab03 --- /dev/null +++ b/tools/testing/selftests/sched_ext/sub_sched.c @@ -0,0 +1,200 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Basic test for sub-sched functionality. + * + * Tests the ability to attach a child scheduler to a specific cgroup + * via the sub_cgroup_id mechanism. + * + * Copyright (c) 2026 Xiaomi Corporation. + */ + +#include <bpf/bpf.h> +#include <errno.h> +#include <fcntl.h> +#include <scx/common.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include "sub_sched_parent.bpf.skel.h" +#include "sub_sched_child.bpf.skel.h" +#include "scx_test.h" + +#define TEST_CGROUP_PATH "/sys/fs/cgroup/test_sub_sched" + +struct test_context { + struct sub_sched_parent *parent_skel; + struct sub_sched_child *child_skel; + char cgroup_path[256]; +}; + +/** + * Create a cgroup v2 for testing. + * Returns the inode number (which serves as cgroup ID) on success, -1 on error. + */ +static u64 create_test_cgroup(const char *path) +{ + struct stat st; + + /* Create the test cgroup directory */ + if (mkdir(path, 0755) < 0) { + if (errno != EEXIST) { + SCX_ERR("Failed to create cgroup: %s", strerror(errno)); + return -1; + } + } + + /* Get the inode number (cgroup ID) */ + if (stat(path, &st) < 0) { + SCX_ERR("Failed to stat cgroup: %s", strerror(errno)); + return -1; + } + + return st.st_ino; +} + +static void cleanup_cgroup(const char *path) +{ + if (rmdir(path) < 0 && errno != ENOENT) + SCX_ERR("Warning: Failed to cleanup cgroup: %s", strerror(errno)); +} + +/** + * Setup: Create cgroup, load both parent and child BPF programs. + */ +static enum scx_test_status setup(void **ctx) +{ + struct test_context *test_ctx; + u64 cgroup_id; + + test_ctx = calloc(1, sizeof(*test_ctx)); + if (!test_ctx) + return SCX_TEST_FAIL; + + /* Create test cgroup */ + snprintf(test_ctx->cgroup_path, sizeof(test_ctx->cgroup_path), + "%s.%d", TEST_CGROUP_PATH, getpid()); + + cgroup_id = create_test_cgroup(test_ctx->cgroup_path); + if (cgroup_id == (u64)-1) { + SCX_ERR("Failed to create test cgroup"); + free(test_ctx); + return SCX_TEST_FAIL; + } + + /* Load parent scheduler */ + test_ctx->parent_skel = sub_sched_parent__open(); + if (!test_ctx->parent_skel) { + SCX_ERR("Failed to open parent BPF skeleton"); + cleanup_cgroup(test_ctx->cgroup_path); + free(test_ctx); + return SCX_TEST_FAIL; + } + + SCX_ENUM_INIT(test_ctx->parent_skel); + if (sub_sched_parent__load(test_ctx->parent_skel)) { + SCX_ERR("Failed to load parent BPF program"); + sub_sched_parent__destroy(test_ctx->parent_skel); + cleanup_cgroup(test_ctx->cgroup_path); + free(test_ctx); + return SCX_TEST_FAIL; + } + + /* Load child scheduler */ + test_ctx->child_skel = sub_sched_child__open(); + if (!test_ctx->child_skel) { + SCX_ERR("Failed to open child BPF skeleton"); + sub_sched_parent__destroy(test_ctx->parent_skel); + cleanup_cgroup(test_ctx->cgroup_path); + free(test_ctx); + return SCX_TEST_FAIL; + } + + /* Set sub_cgroup_id to the test cgroup's inode */ + test_ctx->child_skel->struct_ops.sub_sched_child_ops->sub_cgroup_id = cgroup_id; + + SCX_ENUM_INIT(test_ctx->child_skel); + if (sub_sched_child__load(test_ctx->child_skel)) { + SCX_ERR("Failed to load child BPF program"); + sub_sched_child__destroy(test_ctx->child_skel); + sub_sched_parent__destroy(test_ctx->parent_skel); + cleanup_cgroup(test_ctx->cgroup_path); + free(test_ctx); + return SCX_TEST_FAIL; + } + + *ctx = test_ctx; + return SCX_TEST_PASS; +} + +/** + * Run: Test loading parent, then loading child scheduler. + * + * This tests: + * 1. Parent scheduler loads successfully + * 2. Child scheduler attaches to the specified cgroup + * 3. No crashes or resource leaks + */ +static enum scx_test_status run(void *ctx) +{ + struct test_context *test_ctx = ctx; + struct bpf_link *parent_link; + struct bpf_link *child_link; + + /* Attach parent scheduler */ + parent_link = bpf_map__attach_struct_ops(test_ctx->parent_skel->maps.sub_sched_parent_ops); + if (!parent_link) { + SCX_ERR("Failed to attach parent scheduler"); + return SCX_TEST_FAIL; + } + + /* Attach child scheduler to the cgroup */ + child_link = bpf_map__attach_struct_ops(test_ctx->child_skel->maps.sub_sched_child_ops); + if (!child_link) { + SCX_ERR("Failed to attach child scheduler"); + bpf_link__destroy(parent_link); + return SCX_TEST_FAIL; + } + + /* Let both schedulers run briefly to ensure they don't crash */ + sleep(1); + + /* Detach child first (sub-sched must be detached before parent) */ + bpf_link__destroy(child_link); + + /* Then detach parent */ + bpf_link__destroy(parent_link); + + return SCX_TEST_PASS; +} + +static void cleanup(void *ctx) +{ + struct test_context *test_ctx = ctx; + + if (!test_ctx) + return; + + if (test_ctx->child_skel) + sub_sched_child__destroy(test_ctx->child_skel); + + if (test_ctx->parent_skel) + sub_sched_parent__destroy(test_ctx->parent_skel); + + cleanup_cgroup(test_ctx->cgroup_path); + + free(test_ctx); +} + +struct scx_test sub_sched_basic = { + .name = "sub_sched_basic", + .description = "Test basic sub-sched attachment and detachment", + .setup = setup, + .run = run, + .cleanup = cleanup, +}; + +REGISTER_SCX_TEST(&sub_sched_basic) diff --git a/tools/testing/selftests/sched_ext/sub_sched_child.bpf.c b/tools/testing/selftests/sched_ext/sub_sched_child.bpf.c new file mode 100644 index 000000000000..4df9f049def1 --- /dev/null +++ b/tools/testing/selftests/sched_ext/sub_sched_child.bpf.c @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Child scheduler for sub-sched testing. + * + * This is a minimal scheduler that attaches to a specific cgroup via + * sub_cgroup_id. The BPF loader will populate sub_cgroup_id at load time + * before attaching. + * + * Copyright (c) 2026 Xiaomi Corporation. + */ + +#include <scx/common.bpf.h> + +char _license[] SEC("license") = "GPL"; + +__u64 dispatch_count; + +void BPF_STRUCT_OPS(sub_sched_child_dispatch, s32 cpu, struct task_struct *prev) +{ + /* Minimal dispatch: just return without dispatching anything. + * The kernel will use the default scheduling paths. + */ + __sync_fetch_and_add(&dispatch_count, 1); +} + +SEC(".struct_ops.link") +struct sched_ext_ops sub_sched_child_ops = { + .name = "sub_sched_child", + .sub_cgroup_id = 0, /* Will be set by user space */ + .dispatch = (void *)sub_sched_child_dispatch, +}; diff --git a/tools/testing/selftests/sched_ext/sub_sched_parent.bpf.c b/tools/testing/selftests/sched_ext/sub_sched_parent.bpf.c new file mode 100644 index 000000000000..8b632b31c682 --- /dev/null +++ b/tools/testing/selftests/sched_ext/sub_sched_parent.bpf.c @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Parent scheduler for sub-sched testing. + * + * This scheduler demonstrates the sub_attach/sub_detach ops that allow + * a parent scheduler to manage sub-schedulers attached to specific cgroups. + * + * Copyright (c) 2026 Xiaomi Corporation. + */ + +#include <scx/common.bpf.h> + +char _license[] SEC("license") = "GPL"; + +/* Simple counter for diagnostics */ +__u64 attach_count; +__u64 detach_count; + +s32 BPF_STRUCT_OPS(parent_sub_attach, struct scx_sub_attach_args *args) +{ + __sync_fetch_and_add(&attach_count, 1); + return 0; +} + +void BPF_STRUCT_OPS(parent_sub_detach, struct scx_sub_detach_args *args) +{ + __sync_fetch_and_add(&detach_count, 1); +} + +SEC(".struct_ops.link") +struct sched_ext_ops sub_sched_parent_ops = { + .name = "sub_sched_parent", + .sub_attach = (void *)parent_sub_attach, + .sub_detach = (void *)parent_sub_detach, +}; -- 2.43.0

