diff options
| -rw-r--r-- | nptl/descr.h | 3 | ||||
| -rw-r--r-- | rt/Makefile | 4 | ||||
| -rw-r--r-- | rt/tst-timer-sigmask.c | 7 | ||||
| -rw-r--r-- | rt/tst-timer6.c | 79 | ||||
| -rw-r--r-- | sysdeps/nptl/Makefile | 2 | ||||
| -rw-r--r-- | sysdeps/nptl/fork.h | 2 | ||||
| -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 | 75 | ||||
| -rw-r--r-- | sysdeps/unix/sysv/linux/timer_create.c | 291 | ||||
| -rw-r--r-- | sysdeps/unix/sysv/linux/timer_delete.c | 46 | ||||
| -rw-r--r-- | sysdeps/unix/sysv/linux/timer_routines.c | 154 |
12 files changed, 324 insertions, 349 deletions
diff --git a/nptl/descr.h b/nptl/descr.h index ada6867a19..07eedda067 100644 --- a/nptl/descr.h +++ b/nptl/descr.h @@ -413,6 +413,9 @@ struct pthread /* getrandom vDSO per-thread opaque state. */ void *getrandom_buf; + /* POSIX per-process timer. */ + int timerid; + /* Amount of end padding, if any, in this structure. This definition relies on getrandom_buf being last. */ #define PTHREAD_STRUCT_END_PADDING \ diff --git a/rt/Makefile b/rt/Makefile index 8880e25b64..bdda9dd660 100644 --- a/rt/Makefile +++ b/rt/Makefile @@ -79,7 +79,8 @@ tests := tst-shm tst-timer tst-timer2 \ tst-cpuclock2 tst-cputimer1 tst-cputimer2 tst-cputimer3 \ tst-clock_nanosleep2 \ tst-shm-cancel \ - tst-mqueue10 + tst-mqueue10 \ + tst-timer6 tests-internal := tst-timer-sigmask tests-time64 := \ @@ -101,6 +102,7 @@ include ../Rules CFLAGS-aio_suspend.c += -fexceptions CFLAGS-mq_timedreceive.c += -fexceptions -fasynchronous-unwind-tables CFLAGS-mq_timedsend.c += -fexceptions -fasynchronous-unwind-tables +CFLAGS-timer_create.c += -fexceptions -fasynchronous-unwind-tables # Exclude fortified routines from being built with _FORTIFY_SOURCE routines_no_fortify += \ diff --git a/rt/tst-timer-sigmask.c b/rt/tst-timer-sigmask.c index d8a576bba7..61b7927863 100644 --- a/rt/tst-timer-sigmask.c +++ b/rt/tst-timer-sigmask.c @@ -39,12 +39,9 @@ thread_handler (union sigval sv) for (int sig = 1; sig < NSIG; sig++) { /* POSIX timers threads created to handle SIGEV_THREAD block all - signals except SIGKILL, SIGSTOP and glibc internals ones. */ + signals except SIGKILL, SIGSTOP, and SIGSETXID. */ if (sigismember (&ss, sig)) - { - TEST_VERIFY (sig != SIGKILL && sig != SIGSTOP); - TEST_VERIFY (!is_internal_signal (sig)); - } + TEST_VERIFY (sig != SIGKILL && sig != SIGSTOP && sig != SIGSETXID); if (test_verbose && sigismember (&ss, sig)) printf ("%d, ", sig); } diff --git a/rt/tst-timer6.c b/rt/tst-timer6.c new file mode 100644 index 0000000000..d0f3b030b6 --- /dev/null +++ b/rt/tst-timer6.c @@ -0,0 +1,79 @@ +/* Check re-use timer id for SIGEV_THREAD (BZ 32833) + Copyright (C) 2025 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 <signal.h> +#include <time.h> +#include <support/check.h> + +/* The test depends of the system load and scheduler pressure, so the + number of iteration is arbitrary to not take too much time. */ +enum { niters = 1<<13 }; + +static void +on_good_timer (union sigval sv) +{ +} + +static void +on_bad_timer (union sigval sv) +{ + FAIL_EXIT1 ("triggered bad timer"); +} + +static int +do_test (void) +{ + struct itimerspec its_long = {. it_value = { .tv_sec = 180 } }; + struct itimerspec its_short = { .it_value = { .tv_nsec = 1000 } }; + struct itimerspec its_zero = { .it_interval = { .tv_sec = 0} }; + + struct sigevent ev_short = + { + .sigev_notify = SIGEV_THREAD, + .sigev_notify_function = on_good_timer, + }; + + struct sigevent ev_long = + { + .sigev_notify = SIGEV_THREAD, + .sigev_notify_function = on_bad_timer, + }; + + for (int which = 0; which < niters; which++) + { + struct sigevent * ev = which & 0x1 ? &ev_short : &ev_long; + struct itimerspec * its = which & 0x1? &its_short : &its_long; + + timer_t timerid; + if (timer_create (CLOCK_REALTIME, ev, &timerid) == -1) + FAIL_EXIT1 ("timer_create: %m"); + + if (timer_settime (timerid, 0, its, NULL) == -1) + FAIL_EXIT1 ("timer_settime: %m"); + + if (timer_settime (timerid, 0, &its_zero, NULL) == -1) + FAIL_EXIT1 ("timer_settime: %m"); + + if (timer_delete (timerid) == -1) + FAIL_EXIT1 ("time_delete: %m"); + } + + return 0; +} + +#include <support/test-driver.c> diff --git a/sysdeps/nptl/Makefile b/sysdeps/nptl/Makefile index c6e15d2351..12b7cb5bc2 100644 --- a/sysdeps/nptl/Makefile +++ b/sysdeps/nptl/Makefile @@ -16,8 +16,6 @@ # <https://www.gnu.org/licenses/>. ifeq ($(subdir),rt) -sysdep_routines += timer_routines - tests += tst-mqueue8x CFLAGS-tst-mqueue8x.c += -fexceptions endif diff --git a/sysdeps/nptl/fork.h b/sysdeps/nptl/fork.h index c7b4a195c1..b241ffaffa 100644 --- a/sysdeps/nptl/fork.h +++ b/sysdeps/nptl/fork.h @@ -20,7 +20,6 @@ #define _FORK_H #include <assert.h> -#include <kernel-posix-timers.h> #include <ldsodefs.h> #include <list.h> #include <mqueue.h> @@ -46,7 +45,6 @@ fork_system_setup_after_fork (void) __default_pthread_attr_lock = LLL_LOCK_INITIALIZER; call_function_static_weak (__mq_notify_fork_subprocess); - call_function_static_weak (__timer_fork_subprocess); call_function_static_weak (__getrandom_fork_subprocess); } diff --git a/sysdeps/unix/sysv/linux/internal-signals.h b/sysdeps/unix/sysv/linux/internal-signals.h index ecb00f5f3c..13b840ca08 100644 --- a/sysdeps/unix/sysv/linux/internal-signals.h +++ b/sysdeps/unix/sysv/linux/internal-signals.h @@ -108,12 +108,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 3000953754..046cd3c993 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,12 @@ 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 at least 8-bytes aligned. + + For SIGEV_THREAD, the MSB bit (INT_MAX) is used on timer_delete to + signal the helper thread to stop and issue the timer_delete syscall. */ static inline timer_t kernel_timer_to_timerid (kernel_timer_t ktimerid) @@ -80,7 +45,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,19 +56,33 @@ 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; - else - return (kernel_timer_t) ((uintptr_t) timerid); + { + struct pthread *pthr = timerid_to_pthread (timerid); + return pthr->timerid & INT_MAX; + } + return (uintptr_t) timerid; +} + +static inline void +timerid_signal_delete (kernel_timer_t *timerid) +{ + atomic_fetch_or_relaxed (timerid, INT_MIN); +} + +static inline kernel_timer_t +timerid_clear (kernel_timer_t timerid) +{ + return timerid & INT_MAX; } /* New targets use int instead of timer_t. The difference only diff --git a/sysdeps/unix/sysv/linux/timer_create.c b/sysdeps/unix/sysv/linux/timer_create.c index ca377a69f4..8ec71254a7 100644 --- a/sysdeps/unix/sysv/linux/timer_create.c +++ b/sysdeps/unix/sysv/linux/timer_create.c @@ -15,46 +15,196 @@ 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_create + and the SIGEV_THREAD thread and to instruct the thread to exit if the + timer_create syscall fails. */ + pthread_barrier_t b; + struct sigevent *evp; +}; + +struct cleanup_args_t +{ + struct pthread_unwind_buf *cleanup_jmp_buf; + jmp_buf jb; +}; + +/* Reset internal thread state if the callback issues pthread_exit. It avoids + recreating the thread and having possible unreported missed events due + thread creation failure. */ +static void +timer_helper_thread_cleanup (void *arg) +{ + struct pthread *self = THREAD_SELF; + + /* Call destructors for the thread_local TLS variables. */ + call_function_static_weak (__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); + + /* Reset to the expected initial signal mask. */ + 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; + struct cleanup_args_t clargs = { + .cleanup_jmp_buf = self->cleanup_jmp_buf + }; + + void (*thrfunc) (sigval_t) = args->evp->sigev_notify_function; + sigval_t sival = args->evp->sigev_value; + + __pthread_barrier_wait (&args->b); + /* timer_create syscall failed. */ + if (self->exiting) + return 0; + + 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 (self->timerid < 0) + break; + } + + /* Clear the MSB bit set by timer_delete. */ + INTERNAL_SYSCALL_CALL (timer_delete, timerid_clear (self->timerid)); + + return NULL; +} + +static int +timer_create_sigev_thread (clockid_t clockid, struct sigevent *evp, + timer_t *timerid, pthread_attr_t *attr) +{ + /* Block all signals in the helper thread but SIGSETXID. */ + sigset_t ss; + __sigfillset (&ss); + __sigdelset (&ss, SIGSETXID); + if (__pthread_attr_setsigmask_internal (attr, &ss) < 0) + return -1; + + struct timer_helper_thread_args_t args; + __pthread_barrier_init (&args.b, NULL, 2); + args.evp = evp; + + pthread_t th; + int r = __pthread_create (&th, attr, timer_helper_thread, &args); + if (r != 0) + { + __set_errno (r); + return -1; + } + + struct pthread *pthr = (struct pthread *)th; + struct sigevent kevp = + { + .sigev_value.sival_ptr = NULL, + .sigev_signo = SIGTIMER, + .sigev_notify = SIGEV_THREAD_ID, + ._sigev_un = { ._tid = pthr->tid }, + }; + + kernel_timer_t ktimerid; + if (INLINE_SYSCALL_CALL (timer_create, clockid, &kevp, &ktimerid) < 0) + { + ktimerid = -1; + /* On timer creation failure we need to signal the helper thread to + exit and we can not use the an negative timerid value after the + ptherad_barrier_wait because we can not distinguish between + a timer creation failure and request to delete a timer if it happens + to arrive quickly (for where two timers are create in sequence, + where first succeeds). + + We re-use the 'exiting' member to signal the failure, it is set only + at pthread_create to avoid pthread_kill to send further signals. + Since the thread should not be user-visible, signal are only sent + during timer_delete. */ + pthr->exiting = true; + } + 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 (ktimerid < 0) + return -1; + + *timerid = pthread_to_timerid (th); + + return 0; +} + 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 +214,28 @@ ___timer_create (clockid_t clock_id, struct sigevent *evp, timer_t *timerid) *timerid = kernel_timer_to_timerid (ktimerid); } - else + break; + case SIGEV_THREAD: { - /* 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); + pthread_attr_t 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; - } + __pthread_attr_copy (&attr, evp->sigev_notify_attributes); + else + __pthread_attr_init (&attr); + __pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); - /* 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; - } + int r = timer_create_sigev_thread (syscall_clockid, evp, timerid, + &attr); - /* 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); + if (&attr != evp->sigev_notify_attributes) + __pthread_attr_destroy (&attr); - *timerid = timer_to_timerid (newp); + return r; } - } + default: + __set_errno (EINVAL); + return -1; + } return 0; } |
