Files
linux/tools/testing/selftests/sched_ext/dequeue_remote.c
T
Samuele Mariotti d1350c523f selftests/sched_ext: Fix build error in dequeue_remote selftest
Building the dequeue_remote selftest with newer compilers (e.g., gcc 16)
triggers the following error:

dequeue_remote.c:39:32: error: variable ‘sum’ set but not used

The 'volatile' qualifier prevents the writes from being optimized away,
but does not silence the warning, since 'sum' is indeed only written and
never read.

Consume 'sum' via an empty asm() with a register input constraint, as
already done for the dequeue selftest in commit 3788e32516
("selftests/sched_ext: Fix build error in dequeue selftest"). This forces
the compiler to keep the accumulated value (preserving the CPU stress loop)
and avoids the build error.

Signed-off-by: Samuele Mariotti <smariotti@disroot.org>
Reviewed-by: Kuba Piecuch <jpiecuch@google.com>
Signed-off-by: Tejun Heo <tj@kernel.org>
2026-10-12 07:52:22 -10:00

206 lines
5.2 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Verify that ops.dequeue() is called for tasks leaving BPF custody through
* an SCX-internal cross-CPU migration (move_remote_task_to_local_dsq()).
*
* Copyright (c) 2026 Google LLC.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <sched.h>
#include <bpf/bpf.h>
#include <scx/common.h>
#include <sys/wait.h>
#include "scx_test.h"
#include "dequeue_remote.bpf.skel.h"
#define MAX_WORKERS 64
#define RUN_MS 2000
static int nr_cpus;
static long long now_ms(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * 1000LL + ts.tv_nsec / 1000000;
}
/* mix of short bursts and sleeps to generate lots of enqueues and wakeups */
static void worker_fn(int id)
{
long long end = now_ms() + RUN_MS;
volatile unsigned long sum = 0;
while (now_ms() < end) {
unsigned long j;
for (j = 0; j < 20000 + id * 1000; j++)
sum += j;
asm volatile("" : : "r"(sum));
if (id & 1)
usleep(100);
else
sched_yield();
}
exit(0);
}
static enum scx_test_status run_scenario(struct dequeue_remote *skel,
bool use_move_to_local,
const char *name)
{
enum scx_test_status ret = SCX_TEST_PASS;
struct bpf_link *link;
pid_t pids[MAX_WORKERS];
int nr_workers, nr_forked, i;
nr_workers = 2 * nr_cpus;
if (nr_workers < 4)
nr_workers = 4;
if (nr_workers > MAX_WORKERS)
nr_workers = MAX_WORKERS;
skel->bss->test_use_move_to_local = use_move_to_local;
skel->bss->enqueue_cnt = 0;
skel->bss->dequeue_cnt = 0;
skel->bss->dispatch_dequeue_cnt = 0;
skel->bss->change_dequeue_cnt = 0;
skel->bss->remote_dispatch_cnt = 0;
skel->bss->remote_running_cnt = 0;
skel->bss->missed_dequeue_cnt = 0;
skel->bss->core_sched_exec_dequeue_cnt = 0;
memset(&skel->data->uei, 0, sizeof(skel->data->uei));
link = bpf_map__attach_struct_ops(skel->maps.dequeue_remote_ops);
SCX_FAIL_IF(!link, "Failed to attach struct_ops for %s", name);
fflush(stdout);
fflush(stderr);
for (nr_forked = 0; nr_forked < nr_workers; nr_forked++) {
pid_t pid = fork();
if (pid < 0) {
SCX_ERR("Failed to fork worker %d", nr_forked);
ret = SCX_TEST_FAIL;
break;
}
if (pid == 0)
worker_fn(nr_forked);
pids[nr_forked] = pid;
}
/* on failure, kill the remaining workers but still reap them */
for (i = 0; i < nr_forked; i++) {
int status;
if (ret != SCX_TEST_PASS)
kill(pids[i], SIGKILL);
if (waitpid(pids[i], &status, 0) != pids[i]) {
SCX_ERR("Failed to wait for worker %d", i);
ret = SCX_TEST_FAIL;
} else if (ret == SCX_TEST_PASS && status != 0) {
SCX_ERR("Worker %d exited with status %d", i, status);
ret = SCX_TEST_FAIL;
}
}
bpf_link__destroy(link);
if (ret != SCX_TEST_PASS)
return ret;
printf("%s:\n", name);
printf(" workers: %d\n", nr_workers);
printf(" enqueues: %lu\n", (unsigned long)skel->bss->enqueue_cnt);
printf(" dequeues: %lu (dispatch: %lu, property_change: %lu)\n",
(unsigned long)skel->bss->dequeue_cnt,
(unsigned long)skel->bss->dispatch_dequeue_cnt,
(unsigned long)skel->bss->change_dequeue_cnt);
if (!use_move_to_local)
printf(" remote SCX_DSQ_LOCAL_ON dispatches: %lu\n",
(unsigned long)skel->bss->remote_dispatch_cnt);
printf(" ran on a CPU other than the enqueue CPU: %lu\n",
(unsigned long)skel->bss->remote_running_cnt);
printf(" ran without ops.dequeue(): %lu\n",
(unsigned long)skel->bss->missed_dequeue_cnt);
printf(" late SCX_DEQ_CORE_SCHED_EXEC dequeues: %lu\n",
(unsigned long)skel->bss->core_sched_exec_dequeue_cnt);
if (skel->data->uei.kind != EXIT_KIND(SCX_EXIT_UNREG))
SCX_ERR("Scheduler exited with kind=%lld: %s",
(long long)skel->data->uei.kind, skel->data->uei.msg);
SCX_EQ(skel->data->uei.kind, EXIT_KIND(SCX_EXIT_UNREG));
/* the test is meaningless if no task was moved across CPUs */
SCX_GT(skel->bss->remote_running_cnt, 0);
SCX_EQ(skel->bss->enqueue_cnt, skel->bss->dequeue_cnt);
return SCX_TEST_PASS;
}
static enum scx_test_status setup(void **ctx)
{
struct dequeue_remote *skel;
cpu_set_t cpus;
SCX_FAIL_IF(sched_getaffinity(0, sizeof(cpus), &cpus),
"Failed to get CPU affinity");
nr_cpus = CPU_COUNT(&cpus);
if (nr_cpus < 2) {
fprintf(stderr, "Skipping: requires at least 2 usable CPUs\n");
return SCX_TEST_SKIP;
}
skel = SCX_OPS_OPEN(dequeue_remote_ops, dequeue_remote);
SCX_OPS_LOAD(skel, dequeue_remote_ops, dequeue_remote, uei);
*ctx = skel;
return SCX_TEST_PASS;
}
static enum scx_test_status run(void *ctx)
{
struct dequeue_remote *skel = ctx;
enum scx_test_status status;
status = run_scenario(skel, false,
"BPF queue -> SCX_DSQ_LOCAL_ON | cpu");
if (status != SCX_TEST_PASS)
return status;
status = run_scenario(skel, true,
"user DSQ -> scx_bpf_dsq_move_to_local()");
if (status != SCX_TEST_PASS)
return status;
return SCX_TEST_PASS;
}
static void cleanup(void *ctx)
{
struct dequeue_remote *skel = ctx;
dequeue_remote__destroy(skel);
}
struct scx_test dequeue_remote_test = {
.name = "dequeue_remote",
.description = "Verify ops.dequeue() on SCX-internal cross-CPU migrations",
.setup = setup,
.run = run,
.cleanup = cleanup,
};
REGISTER_SCX_TEST(&dequeue_remote_test)