| /* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ | |
| #ifndef __TASK_LOCAL_DATA_H | |
| #define __TASK_LOCAL_DATA_H | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <sched.h> | |
| #include <stdatomic.h> | |
| #include <stddef.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include <sys/syscall.h> | |
| #include <sys/types.h> | |
| #ifdef TLD_FREE_DATA_ON_THREAD_EXIT | |
| #include <pthread.h> | |
| #endif | |
| #include <bpf/bpf.h> | |
| /* | |
| * OPTIONS | |
| * | |
| * Define the option before including the header. Using different options in | |
| * different translation units is strongly discouraged. | |
| * | |
| * TLD_FREE_DATA_ON_THREAD_EXIT - Frees memory on thread exit automatically | |
| * | |
| * Thread-specific memory for storing TLD is allocated lazily on the first call to | |
| * tld_get_data(). The thread that calls it must also call tld_free() on thread exit | |
| * to prevent memory leak. Pthread will be included if the option is defined. A pthread | |
| * key will be registered with a destructor that calls tld_free(). Enabled only when | |
| * the option is defined and TLD_DEFINE_KEY/tld_create_key() is called in the same | |
| * translation unit. | |
| * | |
| * | |
| * TLD_DYN_DATA_SIZE - The maximum size of memory allocated for TLDs created dynamically | |
| * (default: 64 bytes) | |
| * | |
| * A TLD can be defined statically using TLD_DEFINE_KEY() or created on the fly using | |
| * tld_create_key(). As the total size of TLDs created with tld_create_key() cannot be | |
| * possibly known statically, a memory area of size TLD_DYN_DATA_SIZE will be allocated | |
| * for these TLDs. This additional memory is allocated for every thread that calls | |
| * tld_get_data() even if no tld_create_key are actually called, so be mindful of | |
| * potential memory wastage. Use TLD_DEFINE_KEY() whenever possible as just enough memory | |
| * will be allocated for TLDs created with it. | |
| * | |
| * | |
| * TLD_NAME_LEN - The maximum length of the name of a TLD (default: 62) | |
| * | |
| * Setting TLD_NAME_LEN will affect the maximum number of TLDs a process can store, | |
| * TLD_MAX_DATA_CNT. Must be consistent with task_local_data.bpf.h. | |
| * | |
| * | |
| * TLD_DONT_ROUND_UP_DATA_SIZE - Don't round up memory size allocated for data if | |
| * the memory allocator has low overhead aligned_alloc() implementation. | |
| * | |
| * For some memory allocators, when calling aligned_alloc(alignment, size), size | |
| * does not need to be an integral multiple of alignment and it can be fulfilled | |
| * without using round_up(size, alignment) bytes of memory. Enable this option to | |
| * reduce memory usage. | |
| */ | |
| #define TLD_PAGE_SIZE getpagesize() | |
| #define TLD_PAGE_MASK (~(TLD_PAGE_SIZE - 1)) | |
| #define TLD_ROUND_MASK(x, y) ((__typeof__(x))((y) - 1)) | |
| #define TLD_ROUND_UP(x, y) ((((x) - 1) | TLD_ROUND_MASK(x, y)) + 1) | |
| #define TLD_ROUND_UP_POWER_OF_TWO(x) (1UL << (sizeof(x) * 8 - __builtin_clzl(x - 1))) | |
| #ifndef TLD_DYN_DATA_SIZE | |
| #define TLD_DYN_DATA_SIZE 64 | |
| #endif | |
| #define TLD_MAX_DATA_CNT (TLD_PAGE_SIZE / sizeof(struct tld_metadata) - 1) | |
| #ifndef TLD_NAME_LEN | |
| #define TLD_NAME_LEN 62 | |
| #endif | |
| #ifdef __cplusplus | |
| extern "C" { | |
| #endif | |
| typedef struct { | |
| __s16 off; | |
| } tld_key_t; | |
| struct tld_metadata { | |
| char name[TLD_NAME_LEN]; | |
| _Atomic __u16 size; /* size of tld_data_u->data */ | |
| }; | |
| struct tld_meta_u { | |
| _Atomic __u16 cnt; | |
| __u16 size; | |
| struct tld_metadata metadata[]; | |
| }; | |
| /* | |
| * The unused field ensures map_val.start > 0. On the BPF side, __tld_fetch_key() | |
| * calculates off by summing map_val.start and tld_key_t.off and treats off == 0 | |
| * as key not cached. | |
| */ | |
| struct tld_data_u { | |
| __u64 unused; | |
| char data[] __attribute__((aligned(8))); | |
| }; | |
| struct tld_map_value { | |
| void *data; | |
| struct tld_meta_u *meta; | |
| __u16 start; /* offset of tld_data_u->data in a page */ | |
| }; | |
| struct tld_meta_u * _Atomic tld_meta_p __attribute__((weak)); | |
| __thread struct tld_data_u *tld_data_p __attribute__((weak)); | |
| #ifdef TLD_FREE_DATA_ON_THREAD_EXIT | |
| bool _Atomic tld_pthread_key_init __attribute__((weak)); | |
| pthread_key_t tld_pthread_key __attribute__((weak)); | |
| static void tld_free(void); | |
| static void __tld_thread_exit_handler(void *unused) | |
| { | |
| (void)unused; | |
| tld_free(); | |
| } | |
| #endif | |
| static int __tld_init_meta_p(void) | |
| { | |
| struct tld_meta_u *meta, *uninit = NULL; | |
| int err = 0; | |
| meta = (struct tld_meta_u *)aligned_alloc(TLD_PAGE_SIZE, TLD_PAGE_SIZE); | |
| if (!meta) { | |
| err = -ENOMEM; | |
| goto out; | |
| } | |
| memset(meta, 0, TLD_PAGE_SIZE); | |
| meta->size = TLD_DYN_DATA_SIZE; | |
| if (!atomic_compare_exchange_strong(&tld_meta_p, &uninit, meta)) { | |
| free(meta); | |
| goto out; | |
| } | |
| out: | |
| return err; | |
| } | |
| static int __tld_init_data_p(int map_fd) | |
| { | |
| struct tld_map_value map_val; | |
| struct tld_data_u *data; | |
| int err, tid_fd = -1; | |
| size_t size, size_pot; | |
| tid_fd = syscall(SYS_pidfd_open, sys_gettid(), O_EXCL); | |
| if (tid_fd < 0) { | |
| err = -errno; | |
| goto out; | |
| } | |
| /* | |
| * tld_meta_p->size = TLD_DYN_DATA_SIZE + | |
| * total size of TLDs defined via TLD_DEFINE_KEY() | |
| */ | |
| size = tld_meta_p->size + sizeof(struct tld_data_u); | |
| size_pot = TLD_ROUND_UP_POWER_OF_TWO(size); | |
| #ifdef TLD_DONT_ROUND_UP_DATA_SIZE | |
| data = (struct tld_data_u *)aligned_alloc(size_pot, size); | |
| #else | |
| data = (struct tld_data_u *)aligned_alloc(size_pot, size_pot); | |
| #endif | |
| if (!data) { | |
| err = -ENOMEM; | |
| goto out; | |
| } | |
| /* | |
| * Always pass a page-aligned address to UPTR since the size of tld_map_value::data | |
| * is a page in BTF. | |
| */ | |
| map_val.data = (void *)(TLD_PAGE_MASK & (intptr_t)data); | |
| map_val.start = (~TLD_PAGE_MASK & (intptr_t)data) + sizeof(struct tld_data_u); | |
| map_val.meta = tld_meta_p; | |
| err = bpf_map_update_elem(map_fd, &tid_fd, &map_val, 0); | |
| if (err) { | |
| free(data); | |
| goto out; | |
| } | |
| tld_data_p = data; | |
| #ifdef TLD_FREE_DATA_ON_THREAD_EXIT | |
| pthread_setspecific(tld_pthread_key, (void *)1); | |
| #endif | |
| out: | |
| if (tid_fd >= 0) | |
| close(tid_fd); | |
| return err; | |
| } | |
| static tld_key_t __tld_create_key(const char *name, size_t size, bool dyn_data) | |
| { | |
| int err, i, sz, off = 0; | |
| bool uninit = false; | |
| __u16 cnt; | |
| if (!tld_meta_p) { | |
| err = __tld_init_meta_p(); | |
| if (err) | |
| return (tld_key_t){(__s16)err}; | |
| } | |
| #ifdef TLD_FREE_DATA_ON_THREAD_EXIT | |
| if (atomic_compare_exchange_strong(&tld_pthread_key_init, &uninit, true)) { | |
| err = pthread_key_create(&tld_pthread_key, __tld_thread_exit_handler); | |
| if (err) | |
| return (tld_key_t){(__s16)err}; | |
| } | |
| #endif | |
| for (i = 0; i < (int)TLD_MAX_DATA_CNT; i++) { | |
| retry: | |
| cnt = atomic_load(&tld_meta_p->cnt); | |
| if (i < cnt) { | |
| /* A metadata is not ready until size is updated with a non-zero value */ | |
| while (!(sz = atomic_load(&tld_meta_p->metadata[i].size))) | |
| sched_yield(); | |
| if (!strncmp(tld_meta_p->metadata[i].name, name, TLD_NAME_LEN)) | |
| return (tld_key_t){-EEXIST}; | |
| off += TLD_ROUND_UP(sz, 8); | |
| continue; | |
| } | |
| /* | |
| * TLD_DEFINE_KEY() is given memory upto a page while at most | |
| * TLD_DYN_DATA_SIZE is allocated for tld_create_key() | |
| */ | |
| if (dyn_data) { | |
| if (off + TLD_ROUND_UP(size, 8) > tld_meta_p->size || | |
| tld_meta_p->size > TLD_PAGE_SIZE - sizeof(struct tld_data_u)) | |
| return (tld_key_t){-E2BIG}; | |
| } else { | |
| if (off + TLD_ROUND_UP(size, 8) > TLD_PAGE_SIZE - sizeof(struct tld_data_u)) | |
| return (tld_key_t){-E2BIG}; | |
| tld_meta_p->size += TLD_ROUND_UP(size, 8); | |
| } | |
| /* | |
| * Only one tld_create_key() can increase the current cnt by one and | |
| * takes the latest available slot. Other threads will check again if a new | |
| * TLD can still be added, and then compete for the new slot after the | |
| * succeeding thread update the size. | |
| */ | |
| if (!atomic_compare_exchange_strong(&tld_meta_p->cnt, &cnt, cnt + 1)) | |
| goto retry; | |
| strscpy(tld_meta_p->metadata[i].name, name); | |
| atomic_store(&tld_meta_p->metadata[i].size, size); | |
| return (tld_key_t){(__s16)off}; | |
| } | |
| return (tld_key_t){-ENOSPC}; | |
| } | |
| /** | |
| * TLD_DEFINE_KEY() - Define a TLD and a global variable key associated with the TLD. | |
| * | |
| * @name: The name of the TLD | |
| * @size: The size of the TLD | |
| * @key: The variable name of the key. Cannot exceed TLD_NAME_LEN | |
| * | |
| * The macro can only be used in file scope. | |
| * | |
| * A global variable key of opaque type, tld_key_t, will be declared and initialized before | |
| * main() starts. Use tld_key_is_err() or tld_key_err_or_zero() later to check if the key | |
| * creation succeeded. Pass the key to tld_get_data() to get a pointer to the TLD. | |
| * bpf programs can also fetch the same key by name. | |
| * | |
| * The total size of TLDs created using TLD_DEFINE_KEY() cannot exceed a page. Just | |
| * enough memory will be allocated for each thread on the first call to tld_get_data(). | |
| */ | |
| #define TLD_DEFINE_KEY(key, name, size) \ | |
| tld_key_t key; \ | |
| \ | |
| __attribute__((constructor(101))) \ | |
| void __tld_define_key_##key(void) \ | |
| { \ | |
| key = __tld_create_key(name, size, false); \ | |
| } | |
| /** | |
| * tld_create_key() - Create a TLD and return a key associated with the TLD. | |
| * | |
| * @name: The name the TLD | |
| * @size: The size of the TLD | |
| * | |
| * Return an opaque object key. Use tld_key_is_err() or tld_key_err_or_zero() to check | |
| * if the key creation succeeded. Pass the key to tld_get_data() to get a pointer to | |
| * locate the TLD. bpf programs can also fetch the same key by name. | |
| * | |
| * Use tld_create_key() only when a TLD needs to be created dynamically (e.g., @name is | |
| * not known statically or a TLD needs to be created conditionally) | |
| * | |
| * An additional TLD_DYN_DATA_SIZE bytes are allocated per-thread to accommodate TLDs | |
| * created dynamically with tld_create_key(). Since only a user page is pinned to the | |
| * kernel, when TLDs created with TLD_DEFINE_KEY() uses more than TLD_PAGE_SIZE - | |
| * TLD_DYN_DATA_SIZE, the buffer size will be limited to the rest of the page. | |
| */ | |
| __attribute__((unused)) | |
| static tld_key_t tld_create_key(const char *name, size_t size) | |
| { | |
| return __tld_create_key(name, size, true); | |
| } | |
| __attribute__((unused)) | |
| static inline bool tld_key_is_err(tld_key_t key) | |
| { | |
| return key.off < 0; | |
| } | |
| __attribute__((unused)) | |
| static inline int tld_key_err_or_zero(tld_key_t key) | |
| { | |
| return tld_key_is_err(key) ? key.off : 0; | |
| } | |
| /** | |
| * tld_get_data() - Get a pointer to the TLD associated with the given key of the | |
| * calling thread. | |
| * | |
| * @map_fd: A file descriptor of tld_data_map, the underlying BPF task local storage map | |
| * of task local data. | |
| * @key: A key object created by TLD_DEFINE_KEY() or tld_create_key(). | |
| * | |
| * Return a pointer to the TLD if the key is valid; NULL if not enough memory for TLD | |
| * for this thread, or the key is invalid. The returned pointer is guaranteed to be 8-byte | |
| * aligned. | |
| * | |
| * Threads that call tld_get_data() must call tld_free() on exit to prevent | |
| * memory leak if TLD_FREE_DATA_ON_THREAD_EXIT is not defined. | |
| */ | |
| __attribute__((unused)) | |
| static void *tld_get_data(int map_fd, tld_key_t key) | |
| { | |
| if (!tld_meta_p) | |
| return NULL; | |
| /* tld_data_p is allocated on the first invocation of tld_get_data() */ | |
| if (!tld_data_p && __tld_init_data_p(map_fd)) | |
| return NULL; | |
| return tld_data_p->data + key.off; | |
| } | |
| /** | |
| * tld_free() - Free task local data memory of the calling thread | |
| * | |
| * For the calling thread, all pointers to TLDs acquired before will become invalid. | |
| * | |
| * Users must call tld_free() on thread exit to prevent memory leak. Alternatively, | |
| * define TLD_FREE_DATA_ON_THREAD_EXIT and a thread exit handler will be registered | |
| * to free the memory automatically. Calling tld_free() before thread exit is | |
| * undefined behavior, which may lead to null-pointer dereference. | |
| */ | |
| __attribute__((unused)) | |
| static void tld_free(void) | |
| { | |
| if (tld_data_p) { | |
| free(tld_data_p); | |
| tld_data_p = NULL; | |
| } | |
| } | |
| #ifdef __cplusplus | |
| } /* extern "C" */ | |
| #endif | |
| #endif /* __TASK_LOCAL_DATA_H */ |