-
Notifications
You must be signed in to change notification settings - Fork 120
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <[email protected]>
- Loading branch information
Hou Tao
authored and
Kernel Patches Daemon
committed
Jan 10, 2025
1 parent
6e5fa1c
commit 10d3195
Showing
2 changed files
with
236 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"; |