diff options
| author | jdolecek <jdolecek@NetBSD.org> | 2003-02-15 21:18:59 +0000 |
|---|---|---|
| committer | jdolecek <jdolecek@NetBSD.org> | 2003-02-15 21:18:59 +0000 |
| commit | e6781abf3f59b82fd116aff59ab2ec424beeb98c (patch) | |
| tree | 8dca4de077c673d78807cee32a279b25bb8758a3 /lib/libpthread | |
| parent | 4095b3316d7b28fb0b7e9f463f39c117dcd90f7e (diff) | |
interposition sigtimedwait() with a thread-aware version, which uses
single proxy thread to do the actual syscall, and blocks other threads
in userland
Diffstat (limited to 'lib/libpthread')
| -rw-r--r-- | lib/libpthread/pthread_int.h | 4 | ||||
| -rw-r--r-- | lib/libpthread/pthread_sig.c | 266 |
2 files changed, 266 insertions, 4 deletions
diff --git a/lib/libpthread/pthread_int.h b/lib/libpthread/pthread_int.h index 76b987a1b5e..57dccdf4eda 100644 --- a/lib/libpthread/pthread_int.h +++ b/lib/libpthread/pthread_int.h @@ -1,4 +1,4 @@ -/* $NetBSD: pthread_int.h,v 1.7 2003/02/15 04:34:40 nathanw Exp $ */ +/* $NetBSD: pthread_int.h,v 1.8 2003/02/15 21:18:59 jdolecek Exp $ */ /*- * Copyright (c) 2001 The NetBSD Foundation, Inc. @@ -103,6 +103,8 @@ struct pthread_st { sigset_t pt_sigmask; /* Signals we won't take. */ sigset_t pt_siglist; /* Signals pending for us. */ sigset_t pt_sigblocked; /* Signals delivered while blocked. */ + sigset_t *pt_sigwait; /* Signals waited for in sigwait */ + siginfo_t *pt_wsig; pthread_spin_t pt_siglock; /* Lock on above */ void * pt_exitval; /* Read by pthread_join() */ diff --git a/lib/libpthread/pthread_sig.c b/lib/libpthread/pthread_sig.c index 4f29a692ee0..da2ddd882a1 100644 --- a/lib/libpthread/pthread_sig.c +++ b/lib/libpthread/pthread_sig.c @@ -1,4 +1,4 @@ -/* $NetBSD: pthread_sig.c,v 1.8 2003/02/15 04:38:58 nathanw Exp $ */ +/* $NetBSD: pthread_sig.c,v 1.9 2003/02/15 21:19:00 jdolecek Exp $ */ /*- * Copyright (c) 2001 The NetBSD Foundation, Inc. @@ -86,6 +86,11 @@ static pthread_spin_t pt_sigsuspended_lock; */ static pthread_cond_t pt_sigsuspended_cond = PTHREAD_COND_INITIALIZER; +/* Queue of threads that are waiting in sigtimedwait(). */ +static struct pthread_queue_t pt_sigwaiting; +static pthread_spin_t pt_sigwaiting_lock; +static pthread_t pt_sigwmaster; +static pthread_cond_t pt_sigwaiting_cond = PTHREAD_COND_INITIALIZER; static void pthread__kill(pthread_t, pthread_t, int, int); static void pthread__kill_self(pthread_t, int, int); @@ -94,6 +99,8 @@ static void pthread__signal_tramp(int, int, void (*)(int, int, struct sigcontext *), ucontext_t *, sigset_t *); +static int firstsig(const sigset_t *); + __strong_alias(__libc_thr_sigsetmask,pthread_sigmask) void @@ -101,6 +108,9 @@ pthread__signal_init(void) { SDPRINTF(("(signal_init) setting process sigmask\n")); __sigprocmask14(0, NULL, &pt_process_sigmask); + + PTQ_INIT(&pt_sigsuspended); + PTQ_INIT(&pt_sigwaiting); } int @@ -146,8 +156,8 @@ pthread_kill(pthread_t thread, int sig) /* - * Interpositioning is our friend. We need to intercept sigaction() and - * sigsuspend(). + * Interpositioning is our friend. We need to intercept sigaction(), + * sigsuspend() and sigtimedwait(). */ int @@ -223,6 +233,256 @@ __sigsuspend14(const sigset_t *sigmask) } /* + * Interpositioned sigtimedwait(2), need to multiplex all + * eventual callers to a single kernel lwp. + */ +int _sigtimedwait(const sigset_t * __restrict, siginfo_t * __restrict, + const struct timespec * __restrict); + +static void +pthread_sigtimedwait__callback(void *arg) +{ + pthread__sched(pthread__self(), (pthread_t) arg); +} + +int +sigtimedwait(const sigset_t * __restrict set, siginfo_t * __restrict info, const struct timespec * __restrict timeout) +{ + pthread_t self; + int error = 0; + pthread_t target; + sigset_t wset; + extern int pthread__started; + struct timespec timo; + + /* if threading not started yet, just do the syscall */ + if (__predict_false(pthread__started == 0)) + return (_sigtimedwait(set, info, timeout)); + + self = pthread__self(); + pthread__testcancel(self); + + /* also call syscall if timeout is zero (i.e. polling) */ + if (timeout && timeout->tv_sec == 0 && timeout->tv_nsec == 0) { + error = _sigtimedwait(set, info, timeout); + pthread__testcancel(self); + return (error); + } + + if (timeout) { + if ((u_int) timeout->tv_nsec >= 1000000000) + return (EINVAL); + + timo = *timeout; + } + + pthread_spinlock(self, &pt_sigwaiting_lock); + + /* + * If there is already master thread running, arrange things + * to accomodate for eventual extra signals to wait for, + * and join the sigwaiting list. + */ + if (pt_sigwmaster) { + struct pt_alarm_t timoalarm; + struct timespec etimo; + + /* + * Get current time. We need it if we would become master. + */ + if (timeout) { + clock_gettime(CLOCK_MONOTONIC, &etimo); + timespecadd(&etimo, timeout, &etimo); + } + + /* + * Check if this thread's wait set is different to master set. + */ + wset = *set; + __sigminusset(pt_sigwmaster->pt_sigwait, &wset); + if (firstsig(&wset)) { + /* + * Some new signal in set, wakeup master. It will + * rebuild its wait set. + */ + _lwp_wakeup(pt_sigwmaster->pt_blockedlwp); + } + + /* Save our wait set and info pointer */ + wset = *set; + self->pt_sigwait = &wset; + self->pt_wsig = info; + + /* zero to recognize when we get passed the signal from master */ + info->si_signo = 0; + + if (timeout) { + pthread__alarm_add(self, &timoalarm, &etimo, + pthread_sigtimedwait__callback, self); + } + + block: + pthread_spinlock(self, &self->pt_statelock); + self->pt_state = PT_STATE_BLOCKED_QUEUE; + self->pt_sleepobj = &pt_sigwaiting_cond; + self->pt_sleepq = &pt_sigwaiting; + self->pt_sleeplock = &pt_sigwaiting_lock; + pthread_spinunlock(self, &self->pt_statelock); + + PTQ_INSERT_TAIL(&pt_sigwaiting, self, pt_sleep); + + pthread__block(self, &pt_sigwaiting_lock); + + /* check if we got a signal we waited for */ + if (info->si_signo) { + /* got the signal from master */ + pthread__testcancel(self); + return (0); + } + + /* need the lock from now on */ + pthread_spinlock(self, &pt_sigwaiting_lock); + + /* + * If alarm fired, remove us from queue, adjust master + * wait set and return with EAGAIN. + */ + if (timeout) { + if (pthread__alarm_fired(&timoalarm)) { + PTQ_REMOVE(&pt_sigwaiting, self, pt_sleep); + + /* + * Signal master. It will rebuild it's wait set. + */ + _lwp_wakeup(pt_sigwmaster->pt_blockedlwp); + + pthread_spinunlock(self, &pt_sigwaiting_lock); + errno = EAGAIN; + return (-1); + } + pthread__alarm_del(self, &timoalarm); + } + + /* + * May have been woken up to deliver signal - check if we are + * the master and reblock if appropriate. + */ + if (pt_sigwmaster != self) + goto block; + + /* not signal nor alarm, must have been upgraded to master */ + assert(pt_sigwmaster == self); + + /* update timeout before upgrading to master */ + if (timeout) { + struct timespec tnow; + + clock_gettime(CLOCK_MONOTONIC, &tnow); + /* compute difference to end time */ + timespecsub(&tnow, &etimo, &tnow); + /* substract the difference from timeout */ + timespecsub(&timo, &tnow, &timo); + } + } + + /* MASTER */ + self->pt_sigwait = &wset; + self->pt_wsig = NULL; + + /* Master thread loop */ + pt_sigwmaster = self; + for(;;) { + /* Build our wait set */ + wset = *set; + if (!PTQ_EMPTY(&pt_sigwaiting)) { + PTQ_FOREACH(target, &pt_sigwaiting, pt_sleep) + __sigplusset(target->pt_sigwait, &wset); + } + + pthread_spinunlock(self, &pt_sigwaiting_lock); + + /* + * We are either the only one, or wait set was setup already. + * Just do the syscall now. + */ + error = __sigtimedwait(&wset, info, (timeout) ? &timo : NULL); + + pthread_spinlock(self, &pt_sigwaiting_lock); + if ((error && errno != ECANCELED) + || (!error && __sigismember14(set, info->si_signo)) ) { + /* + * Normal function return. Clear pt_sigwmaster, + * and if wait queue is nonempty, make first waiter + * new master. + */ + pt_sigwmaster = NULL; + if (!PTQ_EMPTY(&pt_sigwaiting)) { + pt_sigwmaster = PTQ_FIRST(&pt_sigwaiting); + PTQ_REMOVE(&pt_sigwaiting, pt_sigwmaster, + pt_sleep); + pthread__sched(self, pt_sigwmaster); + } + + pthread_spinunlock(self, &pt_sigwaiting_lock); + + pthread__testcancel(self); + return (error); + } + + if (!error) { + /* + * Got a signal, but not from _our_ wait set. + * Scan the queue of sigwaiters and wakeup + * the first thread waiting for this signal. + */ + PTQ_FOREACH(target, &pt_sigwaiting, pt_sleep) { + if (__sigismember14(target->pt_sigwait, info->si_signo)) { + assert(target->pt_state==PT_STATE_BLOCKED_QUEUE); + + /* copy to waiter siginfo */ + memcpy(target->pt_wsig, info, sizeof(*info)); + PTQ_REMOVE(&pt_sigwaiting, target, pt_sleep); + pthread__sched(self, target); + break; + } + } + + if (!target) { + /* + * Didn't find anyone waiting on this signal. + * Deliver signal normally. This might + * happen if a thread times out, but + * 'their' signal arrives before the master + * thread would be scheduled after _lwp_wakeup(). + */ + pthread__signal(self, NULL, info->si_signo, + info->si_code); + } else { + /* + * Signal waiter removed, adjust our wait set. + */ + wset = *set; + PTQ_FOREACH(target, &pt_sigwaiting, pt_sleep) + __sigplusset(target->pt_sigwait, &wset); + } + } else { + /* + * ECANCELED - new sigwaiter arrived and added to master + * wait set, or some sigwaiter exited due to timeout + * and removed from master wait set. All the work + * was done already, so just update our timeout + * and go back to syscall. + */ + } + + /* Timeout was adjusted by the syscall, just call again. */ + } + + /* NOTREACHED */ + return (0); +} + +/* * firstsig is stolen from kern_sig.c * XXX this is an abstraction violation; except for this, all of * the knowledge about the composition of sigset_t's was encapsulated |
