diff options
author | Adhemerval Zanella <adhemerval.zanella@linaro.org> | 2023-06-19 14:11:58 -0300 |
---|---|---|
committer | Adhemerval Zanella <adhemerval.zanella@linaro.org> | 2023-06-23 15:28:45 -0300 |
commit | 76d0104cedc5395e6742c0d867260963765d707b (patch) | |
tree | 39f890acbdbb86f283f2a2ea9c755e62b37795ce /sysdeps/unix | |
parent | 99f9ae4ed0ba9f2c84520b78fd0eeed96a7ed40e (diff) | |
download | glibc-azanella/bz30558-posix_timer.tar.gz glibc-azanella/bz30558-posix_timer.tar.xz glibc-azanella/bz30558-posix_timer.zip |
linux: Do not spawn a new thread for SIGEV_THREAD (BZ 30558) azanella/bz30558-posix_timer
Diffstat (limited to 'sysdeps/unix')
-rw-r--r-- | sysdeps/unix/sysv/linux/internal-signals.h | 8 | ||||
-rw-r--r-- | sysdeps/unix/sysv/linux/kernel-posix-cpu-timers.h | 2 | ||||
-rw-r--r-- | sysdeps/unix/sysv/linux/kernel-posix-timers.h | 54 | ||||
-rw-r--r-- | sysdeps/unix/sysv/linux/timer_create.c | 292 | ||||
-rw-r--r-- | sysdeps/unix/sysv/linux/timer_delete.c | 40 | ||||
-rw-r--r-- | sysdeps/unix/sysv/linux/timer_routines.c | 155 |
6 files changed, 211 insertions, 340 deletions
diff --git a/sysdeps/unix/sysv/linux/internal-signals.h b/sysdeps/unix/sysv/linux/internal-signals.h index 43c3c0b4a0..e83b48a58e 100644 --- a/sysdeps/unix/sysv/linux/internal-signals.h +++ b/sysdeps/unix/sysv/linux/internal-signals.h @@ -99,12 +99,4 @@ static const sigset_t sigtimer_set = { } }; -/* Unblock only SIGTIMER. */ -static inline void -signal_unblock_sigtimer (void) -{ - INTERNAL_SYSCALL_CALL (rt_sigprocmask, SIG_UNBLOCK, &sigtimer_set, NULL, - __NSIG_BYTES); -} - #endif diff --git a/sysdeps/unix/sysv/linux/kernel-posix-cpu-timers.h b/sysdeps/unix/sysv/linux/kernel-posix-cpu-timers.h index bea1e0e62d..eda53be167 100644 --- a/sysdeps/unix/sysv/linux/kernel-posix-cpu-timers.h +++ b/sysdeps/unix/sysv/linux/kernel-posix-cpu-timers.h @@ -8,6 +8,8 @@ - A clockid is invalid if bits 2, 1, and 0 are all set. */ +#include <time.h> + #define CPUCLOCK_PID(clock) ((pid_t) ~((clock) >> 3)) #define CPUCLOCK_PERTHREAD(clock) \ (((clock) & (clockid_t) CPUCLOCK_PERTHREAD_MASK) != 0) diff --git a/sysdeps/unix/sysv/linux/kernel-posix-timers.h b/sysdeps/unix/sysv/linux/kernel-posix-timers.h index 9d86bb1dab..cd6bf89acd 100644 --- a/sysdeps/unix/sysv/linux/kernel-posix-timers.h +++ b/sysdeps/unix/sysv/linux/kernel-posix-timers.h @@ -19,29 +19,7 @@ #include <setjmp.h> #include <signal.h> #include <sys/types.h> - - -/* Nonzero if the system calls are not available. */ -extern int __no_posix_timers attribute_hidden; - -/* Callback to start helper thread. */ -extern void __timer_start_helper_thread (void) attribute_hidden; - -/* Control variable for helper thread creation. */ -extern pthread_once_t __timer_helper_once attribute_hidden; - -/* Called from fork so that the new subprocess re-creates the - notification thread if necessary. */ -void __timer_fork_subprocess (void) attribute_hidden; - -/* TID of the helper thread. */ -extern pid_t __timer_helper_tid attribute_hidden; - -/* List of active SIGEV_THREAD timers. */ -extern struct timer *__timer_active_sigev_thread attribute_hidden; - -/* Lock for __timer_active_sigev_thread. */ -extern pthread_mutex_t __timer_active_sigev_thread_lock attribute_hidden; +#include <nptl/descr.h> extern __typeof (timer_create) __timer_create; libc_hidden_proto (__timer_create) @@ -53,25 +31,9 @@ libc_hidden_proto (__timer_getoverrun) /* Type of timers in the kernel. */ typedef int kernel_timer_t; -/* Internal representation of SIGEV_THREAD timer. */ -struct timer -{ - kernel_timer_t ktimerid; - - void (*thrfunc) (sigval_t); - sigval_t sival; - pthread_attr_t attr; - - /* Next element in list of active SIGEV_THREAD timers. */ - struct timer *next; -}; - - /* For !SIGEV_THREAD, the resulting 'timer_t' is the returned kernel timer - identifier (kernel_timer_t), while for SIGEV_THREAD it uses the fact malloc - returns at least _Alignof (max_align_t) pointers plus that valid - kernel_timer_t are always positive to set the MSB bit of the returned - 'timer_t' to indicate the timer handles a SIGEV_THREAD. */ + identifier (kernel_timer_t), while for SIGEV_THREAD it assumes the + pthread_t is always 8-byte aligned. */ static inline timer_t kernel_timer_to_timerid (kernel_timer_t ktimerid) @@ -80,7 +42,7 @@ kernel_timer_to_timerid (kernel_timer_t ktimerid) } static inline timer_t -timer_to_timerid (struct timer *ptr) +pthread_to_timerid (pthread_t ptr) { return (timer_t) (INTPTR_MIN | (uintptr_t) ptr >> 1); } @@ -91,17 +53,17 @@ timer_is_sigev_thread (timer_t timerid) return (intptr_t) timerid < 0; } -static inline struct timer * -timerid_to_timer (timer_t timerid) +static inline struct pthread * +timerid_to_pthread (timer_t timerid) { - return (struct timer *)((uintptr_t) timerid << 1); + return (struct pthread *)((uintptr_t) timerid << 1); } static inline kernel_timer_t timerid_to_kernel_timer (timer_t timerid) { if (timer_is_sigev_thread (timerid)) - return timerid_to_timer (timerid)->ktimerid; + return timerid_to_pthread (timerid)->timerid; else return (kernel_timer_t) ((uintptr_t) timerid); } diff --git a/sysdeps/unix/sysv/linux/timer_create.c b/sysdeps/unix/sysv/linux/timer_create.c index b7e84e2bb1..4d73902589 100644 --- a/sysdeps/unix/sysv/linux/timer_create.c +++ b/sysdeps/unix/sysv/linux/timer_create.c @@ -15,46 +15,198 @@ License along with the GNU C Library; see the file COPYING.LIB. If not, see <https://www.gnu.org/licenses/>. */ -#include <errno.h> -#include <pthread.h> -#include <signal.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> -#include <sysdep.h> -#include <internaltypes.h> +#include <jmpbuf-unwind.h> +#include <kernel-posix-cpu-timers.h> +#include <kernel-posix-timers.h> +#include <ldsodefs.h> +#include <libc-internal.h> +#include <libc-lock.h> #include <pthreadP.h> -#include "kernel-posix-timers.h" -#include "kernel-posix-cpu-timers.h" #include <shlib-compat.h> +struct timer_helper_thread_args_t +{ + /* The barrier is used to synchronize the arguments copy from timer_created + to the timer thread. */ + pthread_barrier_t b; + struct sigevent *evp; +}; + +extern _Noreturn void __longjmp_cancel (__jmp_buf __env, int __val) + attribute_hidden; + +struct cleanup_args_t +{ + struct pthread_unwind_buf *cleanup_jmp_buf; + jmp_buf jb; +}; + +static void +timer_helper_thread_cleanup (void *arg) +{ + struct pthread *self = THREAD_SELF; + + /* Call destructors for the thread_local TLS variables. */ +#ifndef SHARED + if (&__call_tls_dtors != NULL) +#endif + __call_tls_dtors (); + + /* Run the destructor for the thread-local data. */ + __nptl_deallocate_tsd (); + + /* Clean up any state libc stored in thread-local variables. */ + __libc_thread_freeres (); + + /* Reset internal TCB state. */ + struct cleanup_args_t *args = arg; + self->cleanup_jmp_buf = args->cleanup_jmp_buf; + self->cleanup_jmp_buf->priv.data.prev = NULL; + self->cleanup_jmp_buf->priv.data.cleanup = NULL; + self->cleanup_jmp_buf->priv.data.canceltype = 0; + self->cleanup = NULL; + self->exc = (struct _Unwind_Exception) { 0 }; + self->cancelhandling = 0; + self->nextevent = NULL; + + /* Re-initialize the TLS. */ + _dl_allocate_tls_init (TLS_TPADJ (self), true); + + internal_sigset_t ss; + internal_sigfillset (&ss); + internal_sigdelset (&ss, SIGSETXID); + internal_sigprocmask (SIG_SETMASK, &ss, NULL); + + /* There is no need to perform any additional cleanup by the frames. */ + struct __jmp_buf_tag *env = args->jb; + __longjmp (env[0].__jmpbuf, 1); +} + +static void * +timer_helper_thread (void *arg) +{ + struct pthread *self = THREAD_SELF; + + struct timer_helper_thread_args_t *args = arg; + + void (*thrfunc) (sigval_t) = args->evp->sigev_notify_function; + sigval_t sival = args->evp->sigev_value; + __pthread_barrier_wait (&args->b); + + /* timer_create failed. */ + if (self->timerid < 0) + return 0; + + struct cleanup_args_t clargs = { + .cleanup_jmp_buf = self->cleanup_jmp_buf + }; + + while (1) + { + siginfo_t si; + while (__sigwaitinfo (&sigtimer_set, &si) < 0) {}; + + if (si.si_code == SI_TIMER && !setjmp (clargs.jb)) + { + pthread_cleanup_push (timer_helper_thread_cleanup, &clargs); + thrfunc (sival); + pthread_cleanup_pop (0); + } + + /* timer_delete will set the MSB and signal the thread. */ + if (atomic_load_relaxed (&self->timerid) < 0) + break; + } + + /* Clear the MSB bit set by timer_delete. */ + INTERNAL_SYSCALL_CALL (timer_delete, self->timerid & INT_MAX); + + return NULL; +} + +static int +timer_create_sigev_thread (clockid_t syscall_clockid, struct sigevent *evp, + timer_t *timerid) +{ + int ret = -1; + + pthread_attr_t attr; + if (evp->sigev_notify_attributes != NULL) + __pthread_attr_copy (&attr, evp->sigev_notify_attributes); + else + __pthread_attr_init (&attr); + __pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); + + /* Block all signals in the helper thread but SIGSETXID. */ + sigset_t ss; + __sigfillset (&ss); + __sigdelset (&ss, SIGSETXID); + ret = __pthread_attr_setsigmask_internal (&attr, &ss); + if (ret != 0) + goto out; + + struct timer_helper_thread_args_t args; + __pthread_barrier_init (&args.b, NULL, 2); + args.evp = evp; + + pthread_t th; + ret = __pthread_create (&th, &attr, timer_helper_thread, &args); + if (ret != 0) + { + __set_errno (ret); + goto out; + } + + struct sigevent kevp = { + .sigev_value.sival_ptr = NULL, + .sigev_signo = SIGTIMER, + .sigev_notify = SIGEV_THREAD_ID, + ._sigev_un = { ._pad = { [0] = ((struct pthread *)th)->tid } } + }; + + struct pthread *pthr = (struct pthread *)th; + + kernel_timer_t ktimerid; + if (INLINE_SYSCALL_CALL (timer_create, syscall_clockid, &kevp, &ktimerid) < 0) + pthr->timerid = -1; + pthr->timerid = ktimerid; + /* Signal the thread to continue execution after it copies the arguments + or exit if the timer can not be created. */ + __pthread_barrier_wait (&args.b); + + if (timerid >= 0) + *timerid = pthread_to_timerid (th); + + ret = 0; +out: + if (&attr != evp->sigev_notify_attributes) + __pthread_attr_destroy (&attr); + + return ret; +} + int ___timer_create (clockid_t clock_id, struct sigevent *evp, timer_t *timerid) { - { - clockid_t syscall_clockid = (clock_id == CLOCK_PROCESS_CPUTIME_ID - ? PROCESS_CLOCK - : clock_id == CLOCK_THREAD_CPUTIME_ID - ? THREAD_CLOCK - : clock_id); - - /* If the user wants notification via a thread we need to handle - this special. */ - if (evp == NULL - || __builtin_expect (evp->sigev_notify != SIGEV_THREAD, 1)) - { - struct sigevent local_evp; + clockid_t syscall_clockid = (clock_id == CLOCK_PROCESS_CPUTIME_ID + ? PROCESS_CLOCK + : clock_id == CLOCK_THREAD_CPUTIME_ID + ? THREAD_CLOCK + : clock_id); + switch (evp != NULL ? evp->sigev_notify : SIGEV_SIGNAL) + { + case SIGEV_NONE: + case SIGEV_SIGNAL: + case SIGEV_THREAD_ID: + { + struct sigevent kevp; if (evp == NULL) { - /* The kernel has to pass up the timer ID which is a - userlevel object. Therefore we cannot leave it up to - the kernel to determine it. */ - local_evp.sigev_notify = SIGEV_SIGNAL; - local_evp.sigev_signo = SIGALRM; - local_evp.sigev_value.sival_ptr = NULL; - - evp = &local_evp; + kevp.sigev_notify = SIGEV_SIGNAL; + kevp.sigev_signo = SIGALRM; + kevp.sigev_value.sival_ptr = NULL; + evp = &kevp; } kernel_timer_t ktimerid; @@ -64,75 +216,15 @@ ___timer_create (clockid_t clock_id, struct sigevent *evp, timer_t *timerid) *timerid = kernel_timer_to_timerid (ktimerid); } - else - { - /* Create the helper thread. */ - __pthread_once (&__timer_helper_once, __timer_start_helper_thread); - if (__timer_helper_tid == 0) - { - /* No resources to start the helper thread. */ - __set_errno (EAGAIN); - return -1; - } - - struct timer *newp = malloc (sizeof (struct timer)); - if (newp == NULL) - return -1; - - /* Copy the thread parameters the user provided. */ - newp->sival = evp->sigev_value; - newp->thrfunc = evp->sigev_notify_function; - - /* We cannot simply copy the thread attributes since the - implementation might keep internal information for - each instance. */ - __pthread_attr_init (&newp->attr); - if (evp->sigev_notify_attributes != NULL) - { - struct pthread_attr *nattr; - struct pthread_attr *oattr; - - nattr = (struct pthread_attr *) &newp->attr; - oattr = (struct pthread_attr *) evp->sigev_notify_attributes; - - nattr->schedparam = oattr->schedparam; - nattr->schedpolicy = oattr->schedpolicy; - nattr->flags = oattr->flags; - nattr->guardsize = oattr->guardsize; - nattr->stackaddr = oattr->stackaddr; - nattr->stacksize = oattr->stacksize; - } - - /* In any case set the detach flag. */ - __pthread_attr_setdetachstate (&newp->attr, PTHREAD_CREATE_DETACHED); - - /* Create the event structure for the kernel timer. */ - struct sigevent sev = - { .sigev_value.sival_ptr = newp, - .sigev_signo = SIGTIMER, - .sigev_notify = SIGEV_SIGNAL | SIGEV_THREAD_ID, - ._sigev_un = { ._pad = { [0] = __timer_helper_tid } } }; - - /* Create the timer. */ - int res; - res = INTERNAL_SYSCALL_CALL (timer_create, syscall_clockid, &sev, - &newp->ktimerid); - if (INTERNAL_SYSCALL_ERROR_P (res)) - { - free (newp); - __set_errno (INTERNAL_SYSCALL_ERRNO (res)); - return -1; - } - - /* Add to the queue of active timers with thread delivery. */ - __pthread_mutex_lock (&__timer_active_sigev_thread_lock); - newp->next = __timer_active_sigev_thread; - __timer_active_sigev_thread = newp; - __pthread_mutex_unlock (&__timer_active_sigev_thread_lock); - - *timerid = timer_to_timerid (newp); - } - } + break; + case SIGEV_THREAD: + if (timer_create_sigev_thread (syscall_clockid, evp, timerid) < 0) + return -1; + break; + default: + __set_errno (EINVAL); + return -1; + } return 0; } diff --git a/sysdeps/unix/sysv/linux/timer_delete.c b/sysdeps/unix/sysv/linux/timer_delete.c index a611d5013a..ce0c1a777b 100644 --- a/sysdeps/unix/sysv/linux/timer_delete.c +++ b/sysdeps/unix/sysv/linux/timer_delete.c @@ -26,42 +26,18 @@ int ___timer_delete (timer_t timerid) { - kernel_timer_t ktimerid = timerid_to_kernel_timer (timerid); - int res = INLINE_SYSCALL_CALL (timer_delete, ktimerid); - - if (res == 0) + if (timer_is_sigev_thread (timerid)) { - if (timer_is_sigev_thread (timerid)) - { - struct timer *kt = timerid_to_timer (timerid); - - /* Remove the timer from the list. */ - __pthread_mutex_lock (&__timer_active_sigev_thread_lock); - if (__timer_active_sigev_thread == kt) - __timer_active_sigev_thread = kt->next; - else - { - struct timer *prevp = __timer_active_sigev_thread; - while (prevp->next != NULL) - if (prevp->next == kt) - { - prevp->next = kt->next; - break; - } - else - prevp = prevp->next; - } - __pthread_mutex_unlock (&__timer_active_sigev_thread_lock); - - free (kt); - } + struct pthread *th = timerid_to_pthread (timerid); + /* The helper thread itself will be responsible to call the + timer_delete syscall. */ + kernel_timer_t timerid = atomic_load_relaxed (&th->timerid); + atomic_store_relaxed (&th->timerid, timerid | INT_MIN); + __pthread_kill_internal ((pthread_t) th, SIGTIMER); return 0; } - - /* The kernel timer is not known or something else bad happened. - Return the error. */ - return -1; + return INLINE_SYSCALL_CALL (timer_delete, timerid); } versioned_symbol (libc, ___timer_delete, timer_delete, GLIBC_2_34); libc_hidden_ver (___timer_delete, __timer_delete) diff --git a/sysdeps/unix/sysv/linux/timer_routines.c b/sysdeps/unix/sysv/linux/timer_routines.c index 2ad1e7baf2..6e25b021ab 100644 --- a/sysdeps/unix/sysv/linux/timer_routines.c +++ b/sysdeps/unix/sysv/linux/timer_routines.c @@ -1,154 +1 @@ -/* Copyright (C) 2003-2023 Free Software Foundation, Inc. - This file is part of the GNU C Library. - - The GNU C Library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public License as - published by the Free Software Foundation; either version 2.1 of the - License, or (at your option) any later version. - - The GNU C Library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with the GNU C Library; see the file COPYING.LIB. If - not, see <https://www.gnu.org/licenses/>. */ - -#include <errno.h> -#include <setjmp.h> -#include <signal.h> -#include <stdbool.h> -#include <sysdep-cancel.h> -#include <pthreadP.h> -#include "kernel-posix-timers.h" - - -/* List of active SIGEV_THREAD timers. */ -struct timer *__timer_active_sigev_thread; - -/* Lock for _timer_active_sigev_thread. */ -pthread_mutex_t __timer_active_sigev_thread_lock = PTHREAD_MUTEX_INITIALIZER; - -struct thread_start_data -{ - void (*thrfunc) (sigval_t); - sigval_t sival; -}; - - -/* Helper thread to call the user-provided function. */ -static void * -timer_sigev_thread (void *arg) -{ - signal_unblock_sigtimer (); - - struct thread_start_data *td = (struct thread_start_data *) arg; - void (*thrfunc) (sigval_t) = td->thrfunc; - sigval_t sival = td->sival; - - /* The TD object was allocated in timer_helper_thread. */ - free (td); - - /* Call the user-provided function. */ - thrfunc (sival); - - return NULL; -} - - -/* Helper function to support starting threads for SIGEV_THREAD. */ -static _Noreturn void * -timer_helper_thread (void *arg) -{ - /* Endless loop of waiting for signals. The loop is only ended when - the thread is canceled. */ - while (1) - { - siginfo_t si; - - while (__sigwaitinfo (&sigtimer_set, &si) < 0); - if (si.si_code == SI_TIMER) - { - struct timer *tk = (struct timer *) si.si_ptr; - - /* Check the timer is still used and will not go away - while we are reading the values here. */ - __pthread_mutex_lock (&__timer_active_sigev_thread_lock); - - struct timer *runp = __timer_active_sigev_thread; - while (runp != NULL) - if (runp == tk) - break; - else - runp = runp->next; - - if (runp != NULL) - { - struct thread_start_data *td = malloc (sizeof (*td)); - - /* There is not much we can do if the allocation fails. */ - if (td != NULL) - { - /* This is the signal we are waiting for. */ - td->thrfunc = tk->thrfunc; - td->sival = tk->sival; - - pthread_t th; - __pthread_create (&th, &tk->attr, timer_sigev_thread, td); - } - } - - __pthread_mutex_unlock (&__timer_active_sigev_thread_lock); - } - } -} - - -/* Control variable for helper thread creation. */ -pthread_once_t __timer_helper_once = PTHREAD_ONCE_INIT; - - -/* TID of the helper thread. */ -pid_t __timer_helper_tid; - - -/* Reset variables so that after a fork a new helper thread gets started. */ -void -__timer_fork_subprocess (void) -{ - __timer_helper_once = PTHREAD_ONCE_INIT; - __timer_helper_tid = 0; -} - - -void -__timer_start_helper_thread (void) -{ - /* The helper thread needs only very little resources - and should go away automatically when canceled. */ - pthread_attr_t attr; - __pthread_attr_init (&attr); - __pthread_attr_setstacksize (&attr, __pthread_get_minstack (&attr)); - - /* Block all signals in the helper thread but SIGSETXID. */ - sigset_t ss; - __sigfillset (&ss); - __sigdelset (&ss, SIGSETXID); - int res = __pthread_attr_setsigmask_internal (&attr, &ss); - if (res != 0) - { - __pthread_attr_destroy (&attr); - return; - } - - /* Create the helper thread for this timer. */ - pthread_t th; - res = __pthread_create (&th, &attr, timer_helper_thread, NULL); - if (res == 0) - /* We managed to start the helper thread. */ - __timer_helper_tid = ((struct pthread *) th)->tid; - - /* No need for the attribute anymore. */ - __pthread_attr_destroy (&attr); -} +/* Empty. */ |