Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests/bpf: Test concurrent updates on bpf_task_storage_busy

Under full preemptible kernel, task local storage lookup operations on
the same CPU may update per-cpu bpf_task_storage_busy concurrently. If
the update of bpf_task_storage_busy is not preemption safe, the final
value of bpf_task_storage_busy may become not-zero forever and
bpf_task_storage_trylock() will always fail. So add a test case to
ensure the update of bpf_task_storage_busy is preemption safe.

Will skip the test case when CONFIG_PREEMPT is disabled, and it can only
reproduce the problem probabilistically. By increasing
TASK_STORAGE_MAP_NR_LOOP and running it under ARM64 VM with 4-cpus, it
takes about four rounds to reproduce:

> test_maps is modified to only run test_task_storage_map_stress_lookup()
$ export TASK_STORAGE_MAP_NR_THREAD=256
$ export TASK_STORAGE_MAP_NR_LOOP=81920
$ export TASK_STORAGE_MAP_PIN_CPU=1
$ time ./test_maps
test_task_storage_map_stress_lookup(135):FAIL:bad bpf_task_storage_busy got -2

real 0m24.743s
user 0m6.772s
sys 0m17.966s

Signed-off-by: Hou Tao <houtao1@huawei.com>
Acked-by: Alexei Starovoitov <ast@kernel.org>
Link: https://lore.kernel.org/r/20220901061938.3789460-5-houtao@huaweicloud.com
Signed-off-by: Martin KaFai Lau <martin.lau@kernel.org>

authored by

Hou Tao and committed by
Martin KaFai Lau
73b97bc7 c710136e

+161
+122
tools/testing/selftests/bpf/map_tests/task_storage_map.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* Copyright (C) 2022. Huawei Technologies Co., Ltd */ 3 + #define _GNU_SOURCE 4 + #include <sched.h> 5 + #include <unistd.h> 6 + #include <stdlib.h> 7 + #include <stdbool.h> 8 + #include <errno.h> 9 + #include <string.h> 10 + #include <pthread.h> 11 + 12 + #include <bpf/bpf.h> 13 + #include <bpf/libbpf.h> 14 + 15 + #include "test_maps.h" 16 + #include "task_local_storage_helpers.h" 17 + #include "read_bpf_task_storage_busy.skel.h" 18 + 19 + struct lookup_ctx { 20 + bool start; 21 + bool stop; 22 + int pid_fd; 23 + int map_fd; 24 + int loop; 25 + }; 26 + 27 + static void *lookup_fn(void *arg) 28 + { 29 + struct lookup_ctx *ctx = arg; 30 + long value; 31 + int i = 0; 32 + 33 + while (!ctx->start) 34 + usleep(1); 35 + 36 + while (!ctx->stop && i++ < ctx->loop) 37 + bpf_map_lookup_elem(ctx->map_fd, &ctx->pid_fd, &value); 38 + return NULL; 39 + } 40 + 41 + static void abort_lookup(struct lookup_ctx *ctx, pthread_t *tids, unsigned int nr) 42 + { 43 + unsigned int i; 44 + 45 + ctx->stop = true; 46 + ctx->start = true; 47 + for (i = 0; i < nr; i++) 48 + pthread_join(tids[i], NULL); 49 + } 50 + 51 + void test_task_storage_map_stress_lookup(void) 52 + { 53 + #define MAX_NR_THREAD 4096 54 + unsigned int i, nr = 256, loop = 8192, cpu = 0; 55 + struct read_bpf_task_storage_busy *skel; 56 + pthread_t tids[MAX_NR_THREAD]; 57 + struct lookup_ctx ctx; 58 + cpu_set_t old, new; 59 + const char *cfg; 60 + int err; 61 + 62 + cfg = getenv("TASK_STORAGE_MAP_NR_THREAD"); 63 + if (cfg) { 64 + nr = atoi(cfg); 65 + if (nr > MAX_NR_THREAD) 66 + nr = MAX_NR_THREAD; 67 + } 68 + cfg = getenv("TASK_STORAGE_MAP_NR_LOOP"); 69 + if (cfg) 70 + loop = atoi(cfg); 71 + cfg = getenv("TASK_STORAGE_MAP_PIN_CPU"); 72 + if (cfg) 73 + cpu = atoi(cfg); 74 + 75 + skel = read_bpf_task_storage_busy__open_and_load(); 76 + err = libbpf_get_error(skel); 77 + CHECK(err, "open_and_load", "error %d\n", err); 78 + 79 + /* Only for a fully preemptible kernel */ 80 + if (!skel->kconfig->CONFIG_PREEMPT) 81 + return; 82 + 83 + /* Save the old affinity setting */ 84 + sched_getaffinity(getpid(), sizeof(old), &old); 85 + 86 + /* Pinned on a specific CPU */ 87 + CPU_ZERO(&new); 88 + CPU_SET(cpu, &new); 89 + sched_setaffinity(getpid(), sizeof(new), &new); 90 + 91 + ctx.start = false; 92 + ctx.stop = false; 93 + ctx.pid_fd = sys_pidfd_open(getpid(), 0); 94 + ctx.map_fd = bpf_map__fd(skel->maps.task); 95 + ctx.loop = loop; 96 + for (i = 0; i < nr; i++) { 97 + err = pthread_create(&tids[i], NULL, lookup_fn, &ctx); 98 + if (err) { 99 + abort_lookup(&ctx, tids, i); 100 + CHECK(err, "pthread_create", "error %d\n", err); 101 + goto out; 102 + } 103 + } 104 + 105 + ctx.start = true; 106 + for (i = 0; i < nr; i++) 107 + pthread_join(tids[i], NULL); 108 + 109 + skel->bss->pid = getpid(); 110 + err = read_bpf_task_storage_busy__attach(skel); 111 + CHECK(err, "attach", "error %d\n", err); 112 + 113 + /* Trigger program */ 114 + syscall(SYS_gettid); 115 + skel->bss->pid = 0; 116 + 117 + CHECK(skel->bss->busy != 0, "bad bpf_task_storage_busy", "got %d\n", skel->bss->busy); 118 + out: 119 + read_bpf_task_storage_busy__destroy(skel); 120 + /* Restore affinity setting */ 121 + sched_setaffinity(getpid(), sizeof(old), &old); 122 + }
+39
tools/testing/selftests/bpf/progs/read_bpf_task_storage_busy.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* Copyright (C) 2022. Huawei Technologies Co., Ltd */ 3 + #include "vmlinux.h" 4 + #include <bpf/bpf_helpers.h> 5 + #include <bpf/bpf_tracing.h> 6 + 7 + extern bool CONFIG_PREEMPT __kconfig __weak; 8 + extern const int bpf_task_storage_busy __ksym; 9 + 10 + char _license[] SEC("license") = "GPL"; 11 + 12 + int pid = 0; 13 + int busy = 0; 14 + 15 + struct { 16 + __uint(type, BPF_MAP_TYPE_TASK_STORAGE); 17 + __uint(map_flags, BPF_F_NO_PREALLOC); 18 + __type(key, int); 19 + __type(value, long); 20 + } task SEC(".maps"); 21 + 22 + SEC("raw_tp/sys_enter") 23 + int BPF_PROG(read_bpf_task_storage_busy) 24 + { 25 + int *value; 26 + int key; 27 + 28 + if (!CONFIG_PREEMPT) 29 + return 0; 30 + 31 + if (bpf_get_current_pid_tgid() >> 32 != pid) 32 + return 0; 33 + 34 + value = bpf_this_cpu_ptr(&bpf_task_storage_busy); 35 + if (value) 36 + busy = *value; 37 + 38 + return 0; 39 + }