From: Kuba Piecuch <jpiecuch@google.com>
To: Tejun Heo <tj@kernel.org>, Peter Zijlstra <peterz@infradead.org>
Cc: Kuba Piecuch <jpiecuch@google.com>,
David Vernet <void@manifault.com>,
Andrea Righi <arighi@nvidia.com>,
Changwoo Min <changwoo@igalia.com>,
sched-ext@lists.linux.dev, linux-kernel@vger.kernel.org
Subject: [PATCH 2/2] selftests/sched_ext: Test SCX_ENQ_CPU_SELECTED with queued wakeups
Date: Fri, 23 Oct 2026 14:48:05 +0000 [thread overview]
Message-ID: <20261023144811.2230393-3-jpiecuch@google.com> (raw)
In-Reply-To: <20261023144811.2230393-1-jpiecuch@google.com>
Add a test that checks that SCX_ENQ_CPU_SELECTED on a wakeup
ops.enqueue() is set if and only if ops.select_cpu() ran for that wakeup.
The scheduler sets SCX_OPS_ALLOW_QUEUED_WAKEUP so that queued wakeups
are covered too.
ops.select_cpu() records in task storage that it ran and on which CPU.
ops.enqueue() checks that record against SCX_ENQ_CPU_SELECTED. Pairs of
tasks ping-pong over pipes to generate the wakeups. An enqueue on a CPU
other than the one that ran ops.select_cpu() is counted as a queued
wakeup. The test is skipped if none are seen.
This test was written with the help of an AI coding assistant.
Signed-off-by: Kuba Piecuch <jpiecuch@google.com>
---
tools/testing/selftests/sched_ext/Makefile | 1 +
.../sched_ext/enq_cpu_selected.bpf.c | 107 +++++++++++++
.../selftests/sched_ext/enq_cpu_selected.c | 149 ++++++++++++++++++
3 files changed, 257 insertions(+)
create mode 100644 tools/testing/selftests/sched_ext/enq_cpu_selected.bpf.c
create mode 100644 tools/testing/selftests/sched_ext/enq_cpu_selected.c
diff --git a/tools/testing/selftests/sched_ext/Makefile b/tools/testing/selftests/sched_ext/Makefile
index c5d3a2eaea7d..2e21e28426e2 100644
--- a/tools/testing/selftests/sched_ext/Makefile
+++ b/tools/testing/selftests/sched_ext/Makefile
@@ -175,6 +175,7 @@ auto-test-targets := \
ddsp_vtimelocal_fail \
dsp_local_on \
enable_cmask \
+ enq_cpu_selected \
enq_select_cpu \
exit \
hotplug \
diff --git a/tools/testing/selftests/sched_ext/enq_cpu_selected.bpf.c b/tools/testing/selftests/sched_ext/enq_cpu_selected.bpf.c
new file mode 100644
index 000000000000..80e40592332a
--- /dev/null
+++ b/tools/testing/selftests/sched_ext/enq_cpu_selected.bpf.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Verify that SCX_ENQ_CPU_SELECTED is set on a wakeup ops.enqueue() if and
+ * only if ops.select_cpu() was called for that wakeup. The scheduler sets
+ * SCX_OPS_ALLOW_QUEUED_WAKEUP so that the wakeups which are delivered through
+ * the ttwu wakelist (and thus enqueued on a CPU other than the one that ran
+ * ops.select_cpu()) are covered too.
+ */
+
+#include <scx/common.bpf.h>
+
+char _license[] SEC("license") = "GPL";
+
+UEI_DEFINE(uei);
+
+struct task_ctx {
+ bool select_cpu_called;
+ s32 select_cpu_on;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_TASK_STORAGE);
+ __uint(map_flags, BPF_F_NO_PREALLOC);
+ __type(key, int);
+ __type(value, struct task_ctx);
+} task_ctx_stor SEC(".maps");
+
+/*
+ * nr_selected: wakeup enqueues preceded by ops.select_cpu()
+ * nr_queued: of those, enqueues on a CPU other than the one which ran
+ * ops.select_cpu(), i.e. delivered through the wakelist
+ * nr_missing: ops.select_cpu() was called, SCX_ENQ_CPU_SELECTED is clear
+ * nr_spurious: ops.select_cpu() wasn't called, SCX_ENQ_CPU_SELECTED is set
+ */
+u64 nr_selected, nr_queued, nr_missing, nr_spurious;
+
+s32 BPF_STRUCT_OPS(enq_cpu_selected_select_cpu, struct task_struct *p,
+ s32 prev_cpu, u64 wake_flags)
+{
+ struct task_ctx *tctx;
+
+ /* fork enqueues don't carry SCX_ENQ_WAKEUP, only track ttwu */
+ if (!(wake_flags & SCX_WAKE_TTWU))
+ return prev_cpu;
+
+ tctx = bpf_task_storage_get(&task_ctx_stor, p, 0, 0);
+ if (tctx) {
+ tctx->select_cpu_called = true;
+ tctx->select_cpu_on = bpf_get_smp_processor_id();
+ }
+
+ return prev_cpu;
+}
+
+void BPF_STRUCT_OPS(enq_cpu_selected_enqueue, struct task_struct *p,
+ u64 enq_flags)
+{
+ struct task_ctx *tctx;
+
+ tctx = bpf_task_storage_get(&task_ctx_stor, p, 0, 0);
+ if (tctx) {
+ bool called = tctx->select_cpu_called;
+ bool flagged = enq_flags & SCX_ENQ_CPU_SELECTED;
+
+ tctx->select_cpu_called = false;
+
+ if (enq_flags & SCX_ENQ_WAKEUP) {
+ if (called) {
+ __sync_fetch_and_add(&nr_selected, 1);
+ if (bpf_get_smp_processor_id() != tctx->select_cpu_on)
+ __sync_fetch_and_add(&nr_queued, 1);
+ }
+ if (called && !flagged)
+ __sync_fetch_and_add(&nr_missing, 1);
+ if (!called && flagged)
+ __sync_fetch_and_add(&nr_spurious, 1);
+ }
+ }
+
+ scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, enq_flags);
+}
+
+s32 BPF_STRUCT_OPS(enq_cpu_selected_init_task, struct task_struct *p,
+ struct scx_init_task_args *args)
+{
+ if (!bpf_task_storage_get(&task_ctx_stor, p, 0,
+ BPF_LOCAL_STORAGE_GET_F_CREATE))
+ return -ENOMEM;
+
+ return 0;
+}
+
+void BPF_STRUCT_OPS(enq_cpu_selected_exit, struct scx_exit_info *ei)
+{
+ UEI_RECORD(uei, ei);
+}
+
+SEC(".struct_ops.link")
+struct sched_ext_ops enq_cpu_selected_ops = {
+ .select_cpu = (void *)enq_cpu_selected_select_cpu,
+ .enqueue = (void *)enq_cpu_selected_enqueue,
+ .init_task = (void *)enq_cpu_selected_init_task,
+ .exit = (void *)enq_cpu_selected_exit,
+ .flags = SCX_OPS_ALLOW_QUEUED_WAKEUP,
+ .name = "enq_cpu_selected",
+ .timeout_ms = 1000U,
+};
diff --git a/tools/testing/selftests/sched_ext/enq_cpu_selected.c b/tools/testing/selftests/sched_ext/enq_cpu_selected.c
new file mode 100644
index 000000000000..efcec91ce0d4
--- /dev/null
+++ b/tools/testing/selftests/sched_ext/enq_cpu_selected.c
@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Verify that SCX_ENQ_CPU_SELECTED reliably tells ops.enqueue() whether
+ * ops.select_cpu() was called, including for queued (wakelist) wakeups.
+ */
+#include <bpf/bpf.h>
+#include <scx/common.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include "enq_cpu_selected.bpf.skel.h"
+#include "scx_test.h"
+
+#define NR_PAIRS 4
+#define WARMUP_MS 200
+#define RUN_MS 2000
+
+static enum scx_test_status setup(void **ctx)
+{
+ struct enq_cpu_selected *skel;
+
+ skel = enq_cpu_selected__open();
+ SCX_FAIL_IF(!skel, "Failed to open");
+ SCX_ENUM_INIT(skel);
+ SCX_FAIL_IF(enq_cpu_selected__load(skel), "Failed to load skel");
+
+ *ctx = skel;
+
+ return SCX_TEST_PASS;
+}
+
+/* Bounce a byte back and forth between two tasks to generate wakeups. */
+static void pingpong(int rfd, int wfd, bool initiator)
+{
+ char c = 0;
+
+ if (initiator && write(wfd, &c, 1) != 1)
+ exit(1);
+
+ while (true) {
+ if (read(rfd, &c, 1) != 1)
+ exit(1);
+ if (write(wfd, &c, 1) != 1)
+ exit(1);
+ }
+}
+
+static int spawn_pair(pid_t *pids)
+{
+ int a2b[2], b2a[2];
+
+ if (pipe(a2b) || pipe(b2a))
+ return -errno;
+
+ pids[0] = fork();
+ if (pids[0] < 0)
+ return -errno;
+ if (!pids[0])
+ pingpong(b2a[0], a2b[1], true);
+
+ pids[1] = fork();
+ if (pids[1] < 0)
+ return -errno;
+ if (!pids[1])
+ pingpong(a2b[0], b2a[1], false);
+
+ close(a2b[0]);
+ close(a2b[1]);
+ close(b2a[0]);
+ close(b2a[1]);
+
+ return 0;
+}
+
+static enum scx_test_status run(void *ctx)
+{
+ struct enq_cpu_selected *skel = ctx;
+ pid_t pids[NR_PAIRS * 2] = {};
+ enum scx_test_status ret = SCX_TEST_PASS;
+ struct bpf_link *link;
+ int i;
+
+ link = bpf_map__attach_struct_ops(skel->maps.enq_cpu_selected_ops);
+ SCX_FAIL_IF(!link, "Failed to attach scheduler");
+
+ for (i = 0; i < NR_PAIRS; i++) {
+ if (spawn_pair(&pids[i * 2])) {
+ SCX_ERR("Failed to spawn workers");
+ ret = SCX_TEST_FAIL;
+ goto out;
+ }
+ }
+
+ /* skip the wakeups racing the scheduler enable */
+ usleep(WARMUP_MS * 1000);
+ skel->bss->nr_selected = 0;
+ skel->bss->nr_queued = 0;
+ skel->bss->nr_missing = 0;
+ skel->bss->nr_spurious = 0;
+
+ usleep(RUN_MS * 1000);
+
+ fprintf(stderr, "selected=%llu queued=%llu missing=%llu spurious=%llu\n",
+ (unsigned long long)skel->bss->nr_selected,
+ (unsigned long long)skel->bss->nr_queued,
+ (unsigned long long)skel->bss->nr_missing,
+ (unsigned long long)skel->bss->nr_spurious);
+
+ if (skel->bss->nr_missing || skel->bss->nr_spurious) {
+ SCX_ERR("SCX_ENQ_CPU_SELECTED doesn't match ops.select_cpu() calls");
+ ret = SCX_TEST_FAIL;
+ } else if (!skel->bss->nr_queued) {
+ fprintf(stderr, "No queued wakeups observed, skipping\n");
+ ret = SCX_TEST_SKIP;
+ }
+
+ if (skel->data->uei.kind != EXIT_KIND(SCX_EXIT_NONE)) {
+ SCX_ERR("Scheduler exited unexpectedly (kind=%llu)",
+ (unsigned long long)skel->data->uei.kind);
+ ret = SCX_TEST_FAIL;
+ }
+out:
+ for (i = 0; i < NR_PAIRS * 2; i++) {
+ if (pids[i] > 0) {
+ kill(pids[i], SIGKILL);
+ waitpid(pids[i], NULL, 0);
+ }
+ }
+ bpf_link__destroy(link);
+
+ return ret;
+}
+
+static void cleanup(void *ctx)
+{
+ struct enq_cpu_selected *skel = ctx;
+
+ enq_cpu_selected__destroy(skel);
+}
+
+struct scx_test enq_cpu_selected = {
+ .name = "enq_cpu_selected",
+ .description = "Verify SCX_ENQ_CPU_SELECTED on queued wakeups",
+ .setup = setup,
+ .run = run,
+ .cleanup = cleanup,
+};
+REGISTER_SCX_TEST(&enq_cpu_selected)
--
2.56.0.257.gf7497c7d27-goog
next prev parent reply other threads:[~2026-10-23 14:48 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-23 14:48 [PATCH 0/2] sched: Fix SCX_ENQ_CPU_SELECTED for queued wakeups Kuba Piecuch
2026-10-23 14:48 ` [PATCH 1/2] sched/core: Preserve WF_RQ_SELECTED across " Kuba Piecuch
2026-10-23 19:35 ` Tejun Heo
2026-10-23 20:12 ` Andrea Righi
2026-10-23 14:48 ` Kuba Piecuch [this message]
2026-10-23 15:12 ` [PATCH 2/2] selftests/sched_ext: Test SCX_ENQ_CPU_SELECTED with " sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261023144811.2230393-3-jpiecuch@google.com \
--to=jpiecuch@google.com \
--cc=arighi@nvidia.com \
--cc=changwoo@igalia.com \
--cc=linux-kernel@vger.kernel.org \
--cc=peterz@infradead.org \
--cc=sched-ext@lists.linux.dev \
--cc=tj@kernel.org \
--cc=void@manifault.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.