mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
perf mutex: Add a robust, process-shared mutex initializer
A mutex held by a process that can die before unlocking it, like the tests forked by 'perf test' that will take turns using the per user budget set in kernel.perf_event_mlock_kb, has to be taken over by the next locker instead of staying locked forever. Robust mutexes, on top of the kernel's robust futexes, do that: when an owner dies while holding it the kernel marks the futex word owner-died and the next pthread_mutex_lock() returns EOWNERDEAD, then mutex_lock() makes it consistent and takes it over; mutex_trylock() and cond_wait() come back with EOWNERDEAD too, so they take it over the same way, with mutex_trylock() giving the mutex back when it can't be taken over: it is held all the same, and a failed trylock must not leak it. Add mutex_init_pshared_robust(), returning false when robust mutexes aren't available: from libcs without the API, gated by a feature check, to musl and glibc refusing them at runtime, when get_robust_list(2) or set_robust_list(2) aren't usable. glibc only sets a flag in pthread_mutexattr_setrobust(), refusing the mutex itself in pthread_mutex_init() with ENOTSUP instead, other errors from it still being reported. Robust mutexes being unavailable isn't an error, just a debug message for whoever then falls back. Reviewed-by: Ian Rogers <irogers@google.com> Tested-by: Ian Rogers <irogers@google.com> Assisted-by: LLM Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
parent
1938d59c05
commit
4456c404c6
|
|
@ -85,6 +85,7 @@ FEATURE_TESTS_BASIC := \
|
|||
libcpupower \
|
||||
pthread-attr-setaffinity-np \
|
||||
pthread-barrier \
|
||||
pthread-mutex-robust \
|
||||
reallocarray \
|
||||
stackprotector-all \
|
||||
timerfd \
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ FILES= \
|
|||
$(foreach arch,$(LIBUNWIND_ARCHS),test-libunwind-$(arch).bin test-libunwind-debug-frame-$(arch).bin) \
|
||||
test-pthread-attr-setaffinity-np.bin \
|
||||
test-pthread-barrier.bin \
|
||||
test-pthread-mutex-robust.bin \
|
||||
test-stackprotector-all.bin \
|
||||
test-timerfd.bin \
|
||||
test-babeltrace2-ctf-writer.bin \
|
||||
|
|
@ -128,6 +129,9 @@ $(OUTPUT)test-pthread-attr-setaffinity-np.bin:
|
|||
$(OUTPUT)test-pthread-barrier.bin:
|
||||
$(BUILD) -lpthread
|
||||
|
||||
$(OUTPUT)test-pthread-mutex-robust.bin:
|
||||
$(BUILD) -lpthread
|
||||
|
||||
$(OUTPUT)test-stackprotector-all.bin:
|
||||
$(BUILD) -fstack-protector-all
|
||||
|
||||
|
|
|
|||
|
|
@ -90,6 +90,10 @@
|
|||
# include "test-pthread-barrier.c"
|
||||
#undef main
|
||||
|
||||
#define main main_test_pthread_mutex_robust
|
||||
# include "test-pthread-mutex-robust.c"
|
||||
#undef main
|
||||
|
||||
#define main main_test_scandirat
|
||||
# include "test-scandirat.c"
|
||||
#undef main
|
||||
|
|
@ -167,6 +171,7 @@ int main(int argc, char *argv[])
|
|||
main_test_zlib();
|
||||
main_test_pthread_attr_setaffinity_np();
|
||||
main_test_pthread_barrier();
|
||||
main_test_pthread_mutex_robust();
|
||||
main_test_lzma();
|
||||
main_test_bpf();
|
||||
main_test_scandirat();
|
||||
|
|
|
|||
18
tools/build/feature/test-pthread-mutex-robust.c
Normal file
18
tools/build/feature/test-pthread-mutex-robust.c
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include <pthread.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
pthread_mutexattr_t attr;
|
||||
pthread_mutex_t mtx;
|
||||
|
||||
pthread_mutexattr_init(&attr);
|
||||
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
|
||||
pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);
|
||||
pthread_mutex_init(&mtx, &attr);
|
||||
pthread_mutex_consistent(&mtx);
|
||||
pthread_mutex_destroy(&mtx);
|
||||
pthread_mutexattr_destroy(&attr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -417,6 +417,10 @@ ifeq ($(feature-pthread-barrier), 1)
|
|||
CFLAGS += -DHAVE_PTHREAD_BARRIER
|
||||
endif
|
||||
|
||||
ifeq ($(feature-pthread-mutex-robust), 1)
|
||||
CFLAGS += -DHAVE_PTHREAD_MUTEX_ROBUST
|
||||
endif
|
||||
|
||||
ifndef NO_BIONIC
|
||||
$(call feature_check,bionic)
|
||||
ifeq ($(feature-bionic), 1)
|
||||
|
|
|
|||
|
|
@ -17,37 +17,106 @@ static void check_err(const char *fn, int err)
|
|||
|
||||
#define CHECK_ERR(err) check_err(__func__, err)
|
||||
|
||||
static void __mutex_init(struct mutex *mtx, bool pshared, bool recursive)
|
||||
#ifdef HAVE_PTHREAD_MUTEX_ROBUST
|
||||
/*
|
||||
* A robust mutex whose owner died while holding it comes back as EOWNERDEAD,
|
||||
* with the caller holding it: make it consistent, which takes it over; failing
|
||||
* that, a give_up caller unlocks it, leaving it ENOTRECOVERABLE, not leaked.
|
||||
*/
|
||||
static int mutex_take_over(struct mutex *mtx, int err, bool give_up)
|
||||
{
|
||||
if (err != EOWNERDEAD)
|
||||
return err;
|
||||
|
||||
pr_debug("mutex: taking over one left by a dead owner\n");
|
||||
err = pthread_mutex_consistent(&mtx->lock);
|
||||
if (err && give_up)
|
||||
CHECK_ERR(pthread_mutex_unlock(&mtx->lock));
|
||||
|
||||
return err;
|
||||
}
|
||||
#else
|
||||
#define mutex_take_over(mtx, err, give_up) ((void)(mtx), (void)(give_up), (err))
|
||||
#endif
|
||||
|
||||
static bool __mutex_init(struct mutex *mtx, bool pshared, bool recursive, bool robust)
|
||||
{
|
||||
pthread_mutexattr_t attr;
|
||||
bool ok = false, robust_unavailable = false;
|
||||
int err;
|
||||
|
||||
CHECK_ERR(pthread_mutexattr_init(&attr));
|
||||
err = pthread_mutexattr_init(&attr);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
#ifndef NDEBUG
|
||||
/* In normal builds enable error checking, such as recursive usage. */
|
||||
CHECK_ERR(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK));
|
||||
err = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
|
||||
if (err)
|
||||
goto out_destroy;
|
||||
#endif
|
||||
if (recursive)
|
||||
CHECK_ERR(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE));
|
||||
if (pshared)
|
||||
CHECK_ERR(pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED));
|
||||
CHECK_ERR(pthread_mutex_init(&mtx->lock, &attr));
|
||||
if (recursive) {
|
||||
err = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
if (err)
|
||||
goto out_destroy;
|
||||
}
|
||||
if (pshared) {
|
||||
err = pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
|
||||
if (err)
|
||||
goto out_destroy;
|
||||
}
|
||||
if (robust) {
|
||||
#ifdef HAVE_PTHREAD_MUTEX_ROBUST
|
||||
err = pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);
|
||||
if (err)
|
||||
robust_unavailable = true;
|
||||
#else
|
||||
robust_unavailable = true;
|
||||
#endif
|
||||
if (robust_unavailable)
|
||||
goto out_destroy;
|
||||
}
|
||||
|
||||
err = pthread_mutex_init(&mtx->lock, &attr);
|
||||
if (!err)
|
||||
ok = true;
|
||||
else if (robust && pshared && err == ENOTSUP)
|
||||
/*
|
||||
* glibc refuses a robust process shared mutex with ENOTSUP when
|
||||
* set_robust_list(2) isn't usable, i.e. when the machine provides
|
||||
* no robust mutexes for the process shared case: an unavailable
|
||||
* API, not a bad request.
|
||||
*/
|
||||
robust_unavailable = true;
|
||||
out_destroy:
|
||||
CHECK_ERR(pthread_mutexattr_destroy(&attr));
|
||||
out:
|
||||
if (robust_unavailable)
|
||||
pr_debug("mutex: robust mutexes aren't available\n");
|
||||
else if (err)
|
||||
CHECK_ERR(err);
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
void mutex_init(struct mutex *mtx)
|
||||
{
|
||||
__mutex_init(mtx, /*pshared=*/false, /*recursive=*/false);
|
||||
__mutex_init(mtx, /*pshared=*/false, /*recursive=*/false, /*robust=*/false);
|
||||
}
|
||||
|
||||
void mutex_init_pshared(struct mutex *mtx)
|
||||
{
|
||||
__mutex_init(mtx, /*pshared=*/true, /*recursive=*/false);
|
||||
__mutex_init(mtx, /*pshared=*/true, /*recursive=*/false, /*robust=*/false);
|
||||
}
|
||||
|
||||
void mutex_init_recursive(struct mutex *mtx)
|
||||
{
|
||||
__mutex_init(mtx, /*pshared=*/false, /*recursive=*/true);
|
||||
__mutex_init(mtx, /*pshared=*/false, /*recursive=*/true, /*robust=*/false);
|
||||
}
|
||||
|
||||
bool mutex_init_pshared_robust(struct mutex *mtx)
|
||||
{
|
||||
return __mutex_init(mtx, /*pshared=*/true, /*recursive=*/false, /*robust=*/true);
|
||||
}
|
||||
|
||||
void mutex_destroy(struct mutex *mtx)
|
||||
|
|
@ -58,7 +127,8 @@ void mutex_destroy(struct mutex *mtx)
|
|||
void mutex_lock(struct mutex *mtx)
|
||||
NO_THREAD_SAFETY_ANALYSIS
|
||||
{
|
||||
CHECK_ERR(pthread_mutex_lock(&mtx->lock));
|
||||
CHECK_ERR(mutex_take_over(mtx, pthread_mutex_lock(&mtx->lock),
|
||||
/*give_up=*/false));
|
||||
}
|
||||
|
||||
void mutex_unlock(struct mutex *mtx)
|
||||
|
|
@ -69,7 +139,8 @@ void mutex_unlock(struct mutex *mtx)
|
|||
|
||||
bool mutex_trylock(struct mutex *mtx)
|
||||
{
|
||||
int ret = pthread_mutex_trylock(&mtx->lock);
|
||||
int ret = mutex_take_over(mtx, pthread_mutex_trylock(&mtx->lock),
|
||||
/*give_up=*/true);
|
||||
|
||||
if (ret == 0)
|
||||
return true; /* Lock acquired. */
|
||||
|
|
@ -111,7 +182,9 @@ void cond_destroy(struct cond *cnd)
|
|||
|
||||
void cond_wait(struct cond *cnd, struct mutex *mtx)
|
||||
{
|
||||
CHECK_ERR(pthread_cond_wait(&cnd->cond, &mtx->lock));
|
||||
CHECK_ERR(mutex_take_over(mtx,
|
||||
pthread_cond_wait(&cnd->cond, &mtx->lock),
|
||||
/*give_up=*/false));
|
||||
}
|
||||
|
||||
void cond_signal(struct cond *cnd)
|
||||
|
|
|
|||
|
|
@ -142,6 +142,12 @@ void mutex_init(struct mutex *mtx);
|
|||
void mutex_init_pshared(struct mutex *mtx);
|
||||
/* Initializes a mutex that may be recursively held on the same thread. */
|
||||
void mutex_init_recursive(struct mutex *mtx);
|
||||
/*
|
||||
* As mutex_init_pshared(), plus the robust attribute: a process dying while
|
||||
* holding it doesn't leave it locked, but the takeover isn't reported and the
|
||||
* data it protects may be mid update. Returns false when not available.
|
||||
*/
|
||||
bool mutex_init_pshared_robust(struct mutex *mtx);
|
||||
void mutex_destroy(struct mutex *mtx);
|
||||
|
||||
void mutex_lock(struct mutex *mtx) EXCLUSIVE_LOCK_FUNCTION(*mtx);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user