mirror of
https://github.com/torvalds/linux.git
synced 2026-05-21 13:27:57 +02:00
selftests/bpf: Add test case for the freeing of bpf_timer
The main purpose of the test is to demonstrate the lock problem for the free of bpf_timer under PREEMPT_RT. When freeing a bpf_timer which is running on other CPU in bpf_timer_cancel_and_free(), hrtimer_cancel() will try to acquire a spin-lock (namely softirq_expiry_lock), however the freeing procedure has already held a raw-spin-lock. The test first creates two threads: one to start timers and the other to free timers. The start-timers thread will start the timer and then wake up the free-timers thread to free these timers when the starts complete. After freeing, the free-timer thread will wake up the start-timer thread to complete the current iteration. A loop of 10 iterations is used. Signed-off-by: Hou Tao <houtao1@huawei.com> Link: https://lore.kernel.org/r/20250117101816.2101857-6-houtao@huaweicloud.com Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
parent
58f038e6d2
commit
0a5d2efa38
165
tools/testing/selftests/bpf/prog_tests/free_timer.c
Normal file
165
tools/testing/selftests/bpf/prog_tests/free_timer.c
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (C) 2025. Huawei Technologies Co., Ltd */
|
||||
#define _GNU_SOURCE
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <test_progs.h>
|
||||
|
||||
#include "free_timer.skel.h"
|
||||
|
||||
struct run_ctx {
|
||||
struct bpf_program *start_prog;
|
||||
struct bpf_program *overwrite_prog;
|
||||
pthread_barrier_t notify;
|
||||
int loop;
|
||||
bool start;
|
||||
bool stop;
|
||||
};
|
||||
|
||||
static void start_threads(struct run_ctx *ctx)
|
||||
{
|
||||
ctx->start = true;
|
||||
}
|
||||
|
||||
static void stop_threads(struct run_ctx *ctx)
|
||||
{
|
||||
ctx->stop = true;
|
||||
/* Guarantee the order between ->stop and ->start */
|
||||
__atomic_store_n(&ctx->start, true, __ATOMIC_RELEASE);
|
||||
}
|
||||
|
||||
static int wait_for_start(struct run_ctx *ctx)
|
||||
{
|
||||
while (!__atomic_load_n(&ctx->start, __ATOMIC_ACQUIRE))
|
||||
usleep(10);
|
||||
|
||||
return ctx->stop;
|
||||
}
|
||||
|
||||
static void *overwrite_timer_fn(void *arg)
|
||||
{
|
||||
struct run_ctx *ctx = arg;
|
||||
int loop, fd, err;
|
||||
cpu_set_t cpuset;
|
||||
long ret = 0;
|
||||
|
||||
/* Pin on CPU 0 */
|
||||
CPU_ZERO(&cpuset);
|
||||
CPU_SET(0, &cpuset);
|
||||
pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
|
||||
|
||||
/* Is the thread being stopped ? */
|
||||
err = wait_for_start(ctx);
|
||||
if (err)
|
||||
return NULL;
|
||||
|
||||
fd = bpf_program__fd(ctx->overwrite_prog);
|
||||
loop = ctx->loop;
|
||||
while (loop-- > 0) {
|
||||
LIBBPF_OPTS(bpf_test_run_opts, opts);
|
||||
|
||||
/* Wait for start thread to complete */
|
||||
pthread_barrier_wait(&ctx->notify);
|
||||
|
||||
/* Overwrite timers */
|
||||
err = bpf_prog_test_run_opts(fd, &opts);
|
||||
if (err)
|
||||
ret |= 1;
|
||||
else if (opts.retval)
|
||||
ret |= 2;
|
||||
|
||||
/* Notify start thread to start timers */
|
||||
pthread_barrier_wait(&ctx->notify);
|
||||
}
|
||||
|
||||
return (void *)ret;
|
||||
}
|
||||
|
||||
static void *start_timer_fn(void *arg)
|
||||
{
|
||||
struct run_ctx *ctx = arg;
|
||||
int loop, fd, err;
|
||||
cpu_set_t cpuset;
|
||||
long ret = 0;
|
||||
|
||||
/* Pin on CPU 1 */
|
||||
CPU_ZERO(&cpuset);
|
||||
CPU_SET(1, &cpuset);
|
||||
pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
|
||||
|
||||
/* Is the thread being stopped ? */
|
||||
err = wait_for_start(ctx);
|
||||
if (err)
|
||||
return NULL;
|
||||
|
||||
fd = bpf_program__fd(ctx->start_prog);
|
||||
loop = ctx->loop;
|
||||
while (loop-- > 0) {
|
||||
LIBBPF_OPTS(bpf_test_run_opts, opts);
|
||||
|
||||
/* Run the prog to start timer */
|
||||
err = bpf_prog_test_run_opts(fd, &opts);
|
||||
if (err)
|
||||
ret |= 4;
|
||||
else if (opts.retval)
|
||||
ret |= 8;
|
||||
|
||||
/* Notify overwrite thread to do overwrite */
|
||||
pthread_barrier_wait(&ctx->notify);
|
||||
|
||||
/* Wait for overwrite thread to complete */
|
||||
pthread_barrier_wait(&ctx->notify);
|
||||
}
|
||||
|
||||
return (void *)ret;
|
||||
}
|
||||
|
||||
void test_free_timer(void)
|
||||
{
|
||||
struct free_timer *skel;
|
||||
struct bpf_program *prog;
|
||||
struct run_ctx ctx;
|
||||
pthread_t tid[2];
|
||||
void *ret;
|
||||
int err;
|
||||
|
||||
skel = free_timer__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "open_load"))
|
||||
return;
|
||||
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
|
||||
prog = bpf_object__find_program_by_name(skel->obj, "start_timer");
|
||||
if (!ASSERT_OK_PTR(prog, "find start prog"))
|
||||
goto out;
|
||||
ctx.start_prog = prog;
|
||||
|
||||
prog = bpf_object__find_program_by_name(skel->obj, "overwrite_timer");
|
||||
if (!ASSERT_OK_PTR(prog, "find overwrite prog"))
|
||||
goto out;
|
||||
ctx.overwrite_prog = prog;
|
||||
|
||||
pthread_barrier_init(&ctx.notify, NULL, 2);
|
||||
ctx.loop = 10;
|
||||
|
||||
err = pthread_create(&tid[0], NULL, start_timer_fn, &ctx);
|
||||
if (!ASSERT_OK(err, "create start_timer"))
|
||||
goto out;
|
||||
|
||||
err = pthread_create(&tid[1], NULL, overwrite_timer_fn, &ctx);
|
||||
if (!ASSERT_OK(err, "create overwrite_timer")) {
|
||||
stop_threads(&ctx);
|
||||
goto out;
|
||||
}
|
||||
|
||||
start_threads(&ctx);
|
||||
|
||||
ret = NULL;
|
||||
err = pthread_join(tid[0], &ret);
|
||||
ASSERT_EQ(err | (long)ret, 0, "start_timer");
|
||||
ret = NULL;
|
||||
err = pthread_join(tid[1], &ret);
|
||||
ASSERT_EQ(err | (long)ret, 0, "overwrite_timer");
|
||||
out:
|
||||
free_timer__destroy(skel);
|
||||
}
|
||||
71
tools/testing/selftests/bpf/progs/free_timer.c
Normal file
71
tools/testing/selftests/bpf/progs/free_timer.c
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (C) 2025. Huawei Technologies Co., Ltd */
|
||||
#include <linux/bpf.h>
|
||||
#include <time.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
#define MAX_ENTRIES 8
|
||||
|
||||
struct map_value {
|
||||
struct bpf_timer timer;
|
||||
};
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__type(key, int);
|
||||
__type(value, struct map_value);
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
} map SEC(".maps");
|
||||
|
||||
static int timer_cb(void *map, void *key, struct map_value *value)
|
||||
{
|
||||
volatile int sum = 0;
|
||||
int i;
|
||||
|
||||
bpf_for(i, 0, 1024 * 1024) sum += i;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int start_cb(int key)
|
||||
{
|
||||
struct map_value *value;
|
||||
|
||||
value = bpf_map_lookup_elem(&map, (void *)&key);
|
||||
if (!value)
|
||||
return 0;
|
||||
|
||||
bpf_timer_init(&value->timer, &map, CLOCK_MONOTONIC);
|
||||
bpf_timer_set_callback(&value->timer, timer_cb);
|
||||
/* Hope 100us will be enough to wake-up and run the overwrite thread */
|
||||
bpf_timer_start(&value->timer, 100000, BPF_F_TIMER_CPU_PIN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int overwrite_cb(int key)
|
||||
{
|
||||
struct map_value zero = {};
|
||||
|
||||
/* Free the timer which may run on other CPU */
|
||||
bpf_map_update_elem(&map, (void *)&key, &zero, BPF_ANY);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("syscall")
|
||||
int BPF_PROG(start_timer)
|
||||
{
|
||||
bpf_loop(MAX_ENTRIES, start_cb, NULL, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("syscall")
|
||||
int BPF_PROG(overwrite_timer)
|
||||
{
|
||||
bpf_loop(MAX_ENTRIES, overwrite_cb, NULL, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
Loading…
Reference in New Issue
Block a user