mirror of
https://github.com/torvalds/linux.git
synced 2026-04-26 18:42:25 -04:00
When compiling sched_ext selftests using clang 17.0.6, it raised compiler crash and build error: Error at line 68: Unsupport signed division for DAG: 0x55b2f9a60240: i64 = sdiv 0x55b2f9a609b0, Constant:i64<100>, peek_dsq.bpf.c:68:25 @[ peek_dsq.bpf.c:95:4 @[ peek_dsq.bpf.c:169:8 @[ peek _dsq.bpf.c:140:6 ] ] ]Please convert to unsigned div/mod After digging, it's not a compiler error, clang supported Signed division only when using -mcpu=v4, while we use -mcpu=v3 currently, the better way is to use unsigned div, see [1] for details. [1] https://github.com/llvm/llvm-project/issues/70433 Signed-off-by: Zhao Mengmeng <zhaomengmeng@kylinos.cn> Reviewed-by: Andrea Righi <arighi@nvidia.com> Signed-off-by: Tejun Heo <tj@kernel.org>
252 lines
6.4 KiB
C
252 lines
6.4 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* A BPF program for testing DSQ operations and peek in particular.
|
|
*
|
|
* Copyright (c) 2025 Meta Platforms, Inc. and affiliates.
|
|
* Copyright (c) 2025 Ryan Newton <ryan.newton@alum.mit.edu>
|
|
*/
|
|
|
|
#include <scx/common.bpf.h>
|
|
#include <scx/compat.bpf.h>
|
|
|
|
char _license[] SEC("license") = "GPL";
|
|
|
|
UEI_DEFINE(uei); /* Error handling */
|
|
|
|
#define MAX_SAMPLES 100
|
|
#define MAX_CPUS 512
|
|
#define DSQ_POOL_SIZE 8
|
|
int max_samples = MAX_SAMPLES;
|
|
int max_cpus = MAX_CPUS;
|
|
int dsq_pool_size = DSQ_POOL_SIZE;
|
|
|
|
/* Global variables to store test results */
|
|
int dsq_peek_result1 = -1;
|
|
long dsq_inserted_pid = -1;
|
|
int insert_test_cpu = -1; /* Set to the cpu that performs the test */
|
|
long dsq_peek_result2 = -1;
|
|
long dsq_peek_result2_pid = -1;
|
|
long dsq_peek_result2_expected = -1;
|
|
int test_dsq_id = 1234; /* Use a simple ID like create_dsq example */
|
|
int real_dsq_id = 1235; /* DSQ for normal operation */
|
|
int enqueue_count = -1;
|
|
int dispatch_count = -1;
|
|
bool debug_ksym_exists;
|
|
|
|
/* DSQ pool for stress testing */
|
|
int dsq_pool_base_id = 2000;
|
|
int phase1_complete = -1;
|
|
long total_peek_attempts = -1;
|
|
long successful_peeks = -1;
|
|
|
|
/* BPF map for sharing peek results with userspace */
|
|
struct {
|
|
__uint(type, BPF_MAP_TYPE_ARRAY);
|
|
__uint(max_entries, MAX_SAMPLES);
|
|
__type(key, u32);
|
|
__type(value, long);
|
|
} peek_results SEC(".maps");
|
|
|
|
static int get_random_dsq_id(void)
|
|
{
|
|
u64 time = bpf_ktime_get_ns();
|
|
|
|
return dsq_pool_base_id + (time % DSQ_POOL_SIZE);
|
|
}
|
|
|
|
static void record_peek_result(long pid)
|
|
{
|
|
u32 slot_key;
|
|
long *slot_pid_ptr;
|
|
u32 ix;
|
|
|
|
if (pid <= 0)
|
|
return;
|
|
|
|
/* Find an empty slot or one with the same PID */
|
|
bpf_for(ix, 0, 10) {
|
|
slot_key = ((u64)pid + ix) % MAX_SAMPLES;
|
|
slot_pid_ptr = bpf_map_lookup_elem(&peek_results, &slot_key);
|
|
if (!slot_pid_ptr)
|
|
continue;
|
|
|
|
if (*slot_pid_ptr == -1 || *slot_pid_ptr == pid) {
|
|
*slot_pid_ptr = pid;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Scan all DSQs in the pool and try to move a task to local */
|
|
static int scan_dsq_pool(void)
|
|
{
|
|
struct task_struct *task;
|
|
int moved = 0;
|
|
int i;
|
|
|
|
bpf_for(i, 0, DSQ_POOL_SIZE) {
|
|
int dsq_id = dsq_pool_base_id + i;
|
|
|
|
total_peek_attempts++;
|
|
|
|
task = __COMPAT_scx_bpf_dsq_peek(dsq_id);
|
|
if (task) {
|
|
successful_peeks++;
|
|
record_peek_result(task->pid);
|
|
|
|
/* Try to move this task to local */
|
|
if (!moved && scx_bpf_dsq_move_to_local(dsq_id) == 0) {
|
|
moved = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return moved;
|
|
}
|
|
|
|
/* Struct_ops scheduler for testing DSQ peek operations */
|
|
void BPF_STRUCT_OPS(peek_dsq_enqueue, struct task_struct *p, u64 enq_flags)
|
|
{
|
|
struct task_struct *peek_result;
|
|
int last_insert_test_cpu, cpu;
|
|
|
|
enqueue_count++;
|
|
cpu = bpf_get_smp_processor_id();
|
|
last_insert_test_cpu = __sync_val_compare_and_swap(&insert_test_cpu, -1, cpu);
|
|
|
|
/* Phase 1: Simple insert-then-peek test (only on first task) */
|
|
if (last_insert_test_cpu == -1) {
|
|
bpf_printk("peek_dsq_enqueue beginning phase 1 peek test on cpu %d", cpu);
|
|
|
|
/* Test 1: Peek empty DSQ - should return NULL */
|
|
peek_result = __COMPAT_scx_bpf_dsq_peek(test_dsq_id);
|
|
dsq_peek_result1 = (long)peek_result; /* Should be 0 (NULL) */
|
|
|
|
/* Test 2: Insert task into test DSQ for testing in dispatch callback */
|
|
dsq_inserted_pid = p->pid;
|
|
scx_bpf_dsq_insert(p, test_dsq_id, 0, enq_flags);
|
|
dsq_peek_result2_expected = (long)p; /* Expected the task we just inserted */
|
|
} else if (!phase1_complete) {
|
|
/* Still in phase 1, use real DSQ */
|
|
scx_bpf_dsq_insert(p, real_dsq_id, 0, enq_flags);
|
|
} else {
|
|
/* Phase 2: Random DSQ insertion for stress testing */
|
|
int random_dsq_id = get_random_dsq_id();
|
|
|
|
scx_bpf_dsq_insert(p, random_dsq_id, 0, enq_flags);
|
|
}
|
|
}
|
|
|
|
void BPF_STRUCT_OPS(peek_dsq_dispatch, s32 cpu, struct task_struct *prev)
|
|
{
|
|
dispatch_count++;
|
|
|
|
/* Phase 1: Complete the simple peek test if we inserted a task but
|
|
* haven't tested peek yet
|
|
*/
|
|
if (insert_test_cpu == cpu && dsq_peek_result2 == -1) {
|
|
struct task_struct *peek_result;
|
|
|
|
bpf_printk("peek_dsq_dispatch completing phase 1 peek test on cpu %d", cpu);
|
|
|
|
/* Test 3: Peek DSQ after insert - should return the task we inserted */
|
|
peek_result = __COMPAT_scx_bpf_dsq_peek(test_dsq_id);
|
|
/* Store the PID of the peeked task for comparison */
|
|
dsq_peek_result2 = (long)peek_result;
|
|
dsq_peek_result2_pid = peek_result ? peek_result->pid : -1;
|
|
|
|
/* Now consume the task since we've peeked at it */
|
|
scx_bpf_dsq_move_to_local(test_dsq_id);
|
|
|
|
/* Mark phase 1 as complete */
|
|
phase1_complete = 1;
|
|
bpf_printk("Phase 1 complete, starting phase 2 stress testing");
|
|
} else if (!phase1_complete) {
|
|
/* Still in phase 1, use real DSQ */
|
|
scx_bpf_dsq_move_to_local(real_dsq_id);
|
|
} else {
|
|
/* Phase 2: Scan all DSQs in the pool and try to move a task */
|
|
if (!scan_dsq_pool()) {
|
|
/* No tasks found in DSQ pool, fall back to real DSQ */
|
|
scx_bpf_dsq_move_to_local(real_dsq_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
s32 BPF_STRUCT_OPS_SLEEPABLE(peek_dsq_init)
|
|
{
|
|
s32 err;
|
|
int i;
|
|
|
|
/* Always set debug values so we can see which version we're using */
|
|
debug_ksym_exists = bpf_ksym_exists(scx_bpf_dsq_peek) ? 1 : 0;
|
|
|
|
/* Initialize state first */
|
|
insert_test_cpu = -1;
|
|
enqueue_count = 0;
|
|
dispatch_count = 0;
|
|
phase1_complete = 0;
|
|
total_peek_attempts = 0;
|
|
successful_peeks = 0;
|
|
|
|
/* Create the test and real DSQs */
|
|
err = scx_bpf_create_dsq(test_dsq_id, -1);
|
|
if (err) {
|
|
scx_bpf_error("Failed to create DSQ %d: %d", test_dsq_id, err);
|
|
return err;
|
|
}
|
|
err = scx_bpf_create_dsq(real_dsq_id, -1);
|
|
if (err) {
|
|
scx_bpf_error("Failed to create DSQ %d: %d", test_dsq_id, err);
|
|
return err;
|
|
}
|
|
|
|
/* Create the DSQ pool for stress testing */
|
|
bpf_for(i, 0, DSQ_POOL_SIZE) {
|
|
int dsq_id = dsq_pool_base_id + i;
|
|
|
|
err = scx_bpf_create_dsq(dsq_id, -1);
|
|
if (err) {
|
|
scx_bpf_error("Failed to create DSQ pool entry %d: %d", dsq_id, err);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
/* Initialize the peek results map */
|
|
bpf_for(i, 0, MAX_SAMPLES) {
|
|
u32 key = i;
|
|
long pid = -1;
|
|
|
|
bpf_map_update_elem(&peek_results, &key, &pid, BPF_ANY);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void BPF_STRUCT_OPS(peek_dsq_exit, struct scx_exit_info *ei)
|
|
{
|
|
int i;
|
|
|
|
/* Destroy the primary DSQs */
|
|
scx_bpf_destroy_dsq(test_dsq_id);
|
|
scx_bpf_destroy_dsq(real_dsq_id);
|
|
|
|
/* Destroy the DSQ pool */
|
|
bpf_for(i, 0, DSQ_POOL_SIZE) {
|
|
int dsq_id = dsq_pool_base_id + i;
|
|
|
|
scx_bpf_destroy_dsq(dsq_id);
|
|
}
|
|
|
|
UEI_RECORD(uei, ei);
|
|
}
|
|
|
|
SEC(".struct_ops.link")
|
|
struct sched_ext_ops peek_dsq_ops = {
|
|
.enqueue = (void *)peek_dsq_enqueue,
|
|
.dispatch = (void *)peek_dsq_dispatch,
|
|
.init = (void *)peek_dsq_init,
|
|
.exit = (void *)peek_dsq_exit,
|
|
.name = "peek_dsq",
|
|
};
|