diff options
Diffstat (limited to 'lib/libkse/thread/thr_sig.c')
-rw-r--r-- | lib/libkse/thread/thr_sig.c | 1242 |
1 files changed, 829 insertions, 413 deletions
diff --git a/lib/libkse/thread/thr_sig.c b/lib/libkse/thread/thr_sig.c index 86ded7f..8a9aadf 100644 --- a/lib/libkse/thread/thr_sig.c +++ b/lib/libkse/thread/thr_sig.c @@ -44,46 +44,47 @@ #include "pthread_private.h" /* Prototypes: */ -static void thread_sig_check_state(pthread_t pthread, int sig); -static void thread_sig_finish_longjmp(void *arg); -static void handle_state_change(pthread_t pthread); - +static void thread_sig_add(pthread_t pthread, int sig, int has_args); +static pthread_t thread_sig_find(int sig); +static void thread_sig_handle_special(int sig); +static void thread_sig_savecontext(pthread_t pthread, ucontext_t *ucp); +static void thread_sigframe_add(pthread_t thread, int sig); +static void thread_sigframe_leave(pthread_t thread, int frame); +static void thread_sigframe_restore(pthread_t thread, struct pthread_signal_frame *psf); +static void thread_sigframe_save(pthread_t thread, struct pthread_signal_frame *psf); + +/* #define DEBUG_SIGNAL */ +#ifdef DEBUG_SIGNAL +#define DBG_MSG stdout_debug +#else +#define DBG_MSG(x...) +#endif -/* Static variables: */ -static spinlock_t signal_lock = _SPINLOCK_INITIALIZER; -static unsigned int pending_sigs[NSIG]; -static unsigned int handled_sigs[NSIG]; -static int volatile check_pending = 0; -static int volatile check_waiting = 0; +#if defined(_PTHREADS_INVARIANTS) +#define SIG_SET_ACTIVE() _sig_in_handler = 1 +#define SIG_SET_INACTIVE() _sig_in_handler = 0 +#else +#define SIG_SET_ACTIVE() +#define SIG_SET_INACTIVE() +#endif -/* Initialize signal handling facility: */ void -_thread_sig_init(void) +_thread_sig_handler(int sig, siginfo_t *info, ucontext_t *ucp) { - int i; + pthread_t pthread; + int current_frame; + char c; - /* Clear pending and handled signal counts: */ - for (i = 1; i < NSIG; i++) { - pending_sigs[i - 1] = 0; - handled_sigs[i - 1] = 0; - } - - /* Clear the lock: */ - signal_lock.access_lock = 0; - - /* Clear the process pending signals: */ - sigemptyset(&_process_sigpending); -} - -void -_thread_sig_handler(int sig, int code, ucontext_t * scp) -{ - pthread_t pthread, pthread_next; - int i; - char c; + if (ucp == NULL) + PANIC("Thread signal handler received null context"); + DBG_MSG("Got signal %d, current thread %p\n", sig, _thread_run); /* Check if an interval timer signal: */ if (sig == _SCHED_SIGNAL) { + /* Update the scheduling clock: */ + gettimeofday((struct timeval *)&_sched_tod, NULL); + _sched_ticks++; + if (_thread_kern_in_sched != 0) { /* * The scheduler is already running; ignore this @@ -97,14 +98,18 @@ _thread_sig_handler(int sig, int code, ucontext_t * scp) */ else if (_thread_run->sig_defer_count > 0) _thread_run->yield_on_sig_undefer = 1; - else { /* + * Save the context of the currently running thread: + */ + thread_sig_savecontext(_thread_run, ucp); + + /* * Schedule the next thread. This function is not * expected to return because it will do a longjmp * instead. */ - _thread_kern_sched(scp); + _thread_kern_sched(ucp); /* * This point should not be reached, so abort the @@ -118,8 +123,8 @@ _thread_sig_handler(int sig, int code, ucontext_t * scp) * is accessing the scheduling queues or if there is a currently * running thread that has deferred signals. */ - else if ((_queue_signals != 0) || ((_thread_kern_in_sched == 0) && - (_thread_run->sig_defer_count > 0))) { + else if ((_thread_kern_in_sched != 0) || + (_thread_run->sig_defer_count > 0)) { /* Cast the signal number to a character variable: */ c = sig; @@ -127,117 +132,150 @@ _thread_sig_handler(int sig, int code, ucontext_t * scp) * Write the signal number to the kernel pipe so that it will * be ready to read when this signal handler returns. */ - _thread_sys_write(_thread_kern_pipe[1], &c, 1); + if (_queue_signals != 0) { + _thread_sys_write(_thread_kern_pipe[1], &c, 1); + DBG_MSG("Got signal %d, queueing to kernel pipe\n", sig); + } + if (_thread_sigq[sig - 1].blocked == 0) { + DBG_MSG("Got signal %d, adding to _thread_sigq\n", sig); + /* + * Do not block this signal; it will be blocked + * when the pending signals are run down. + */ + /* _thread_sigq[sig - 1].blocked = 1; */ - /* Indicate that there are queued signals in the pipe. */ - _sigq_check_reqd = 1; - } else { - if (_atomic_lock(&signal_lock.access_lock)) { - /* There is another signal handler running: */ - pending_sigs[sig - 1]++; - check_pending = 1; + /* + * Queue the signal, saving siginfo and sigcontext + * (ucontext). + * + * XXX - Do we need to copy siginfo and ucp? + */ + _thread_sigq[sig - 1].signo = sig; + if (info != NULL) + memcpy(&_thread_sigq[sig - 1].siginfo, info, + sizeof(*info)); + memcpy(&_thread_sigq[sig - 1].uc, ucp, sizeof(*ucp)); + + /* Indicate that there are queued signals: */ + _thread_sigq[sig - 1].pending = 1; + _sigq_check_reqd = 1; } - else { - /* It's safe to handle the signal now. */ - pthread = _thread_sig_handle(sig, scp); + /* These signals need special handling: */ + else if (sig == SIGCHLD || sig == SIGTSTP || + sig == SIGTTIN || sig == SIGTTOU) { + _thread_sigq[sig - 1].pending = 1; + _thread_sigq[sig - 1].signo = sig; + _sigq_check_reqd = 1; + } + else + DBG_MSG("Got signal %d, ignored.\n", sig); + } + /* + * The signal handlers should have been installed so that they + * cannot be interrupted by other signals. + */ + else if (_thread_sigq[sig - 1].blocked == 0) { + /* The signal is not blocked; handle the signal: */ + current_frame = _thread_run->sigframe_count; - /* Reset the pending and handled count back to 0: */ - pending_sigs[sig - 1] = 0; - handled_sigs[sig - 1] = 0; + /* + * Ignore subsequent occurrences of this signal + * until the current signal is handled: + */ + _thread_sigq[sig - 1].blocked = 1; - if (pthread == NULL) - signal_lock.access_lock = 0; - else { - sigaddset(&pthread->sigmask, sig); + /* This signal will be handled; clear the pending flag: */ + _thread_sigq[sig - 1].pending = 0; - /* - * Make sure not to deliver the same signal to - * the thread twice. sigpend is potentially - * modified by the call chain - * _thread_sig_handle() --> - * thread_sig_check_state(), which can happen - * just above. - */ - if (sigismember(&pthread->sigpend, sig)) - sigdelset(&pthread->sigpend, sig); + /* + * Save siginfo and sigcontext (ucontext). + * + * XXX - Do we need to copy siginfo and ucp? + */ + _thread_sigq[sig - 1].signo = sig; - signal_lock.access_lock = 0; - _thread_sig_deliver(pthread, sig); - sigdelset(&pthread->sigmask, sig); - } - } + if (info != NULL) + memcpy(&_thread_sigq[sig - 1].siginfo, info, + sizeof(*info)); + memcpy(&_thread_sigq[sig - 1].uc, ucp, sizeof(*ucp)); + SIG_SET_ACTIVE(); - /* Enter a loop to process pending signals: */ - while ((check_pending != 0) && - (_atomic_lock(&signal_lock.access_lock) == 0)) { - check_pending = 0; - for (i = 1; i < NSIG; i++) { - if (pending_sigs[i - 1] > handled_sigs[i - 1]) { - pending_sigs[i - 1] = handled_sigs[i - 1]; - pthread = _thread_sig_handle(i, scp); - if (pthread != NULL) { - sigaddset(&pthread->sigmask, i); - /* Save the old state: */ - pthread->oldstate = pthread->state; - signal_lock.access_lock = 0; - _thread_sig_deliver(pthread, i); - sigdelset(&pthread->sigmask, i); - if (_atomic_lock(&signal_lock.access_lock)) { - check_pending = 1; - /* - * Have the lock holder take care - * of any state changes: - */ - if (pthread->state != pthread->oldstate) - check_waiting = 1; - return; - } - if (pthread->state != pthread->oldstate) - handle_state_change(pthread); - } - } - } - while (check_waiting != 0) { - check_waiting = 0; - /* - * Enter a loop to wake up all threads waiting - * for a process to complete: - */ - for (pthread = TAILQ_FIRST(&_waitingq); - pthread != NULL; pthread = pthread_next) { - pthread_next = TAILQ_NEXT(pthread, pqe); - if (pthread->state == PS_RUNNING) - handle_state_change(pthread); - } - } - /* Release the lock: */ - signal_lock.access_lock = 0; + /* Handle special signals: */ + thread_sig_handle_special(sig); + + if ((pthread = thread_sig_find(sig)) != NULL) { + DBG_MSG("Got signal %d, adding frame to thread %p\n", + sig, pthread); + /* + * A thread was found that can handle the signal. + * Save the context of the currently running thread + * so that we can switch to another thread without + * losing track of where the current thread left off. + * This also applies if the current thread is the + * thread to be signaled. + */ + thread_sig_savecontext(_thread_run, ucp); + + /* Setup the target thread to receive the signal: */ + thread_sig_add(pthread, sig, /*has_args*/ 1); + + /* Take a peek at the next ready to run thread: */ + pthread = PTHREAD_PRIOQ_FIRST(); + DBG_MSG("Finished adding frame, head of prio list %p\n", + pthread); } + else + DBG_MSG("No thread to handle signal %d\n", sig); + SIG_SET_INACTIVE(); /* - * Check to see if the current thread performed a - * [sig|_]longjmp() out of a signal handler. + * Switch to a different context if the currently running + * thread takes a signal, or if another thread takes a + * signal and the currently running thread is not in a + * signal handler. */ - if ((_thread_run->jmpflags & (JMPFLAGS_LONGJMP | - JMPFLAGS__LONGJMP)) != 0) { - _thread_run->jmpflags = JMPFLAGS_NONE; - __longjmp(_thread_run->nested_jmp.jmp, - _thread_run->longjmp_val); - } else if ((_thread_run->jmpflags & JMPFLAGS_SIGLONGJMP) != 0) { - _thread_run->jmpflags = JMPFLAGS_NONE; - __siglongjmp(_thread_run->nested_jmp.sigjmp, - _thread_run->longjmp_val); + if ((_thread_run->sigframe_count > current_frame) || + ((pthread != NULL) && + (pthread->active_priority > _thread_run->active_priority))) { + /* Enter the kernel scheduler: */ + DBG_MSG("Entering scheduler from signal handler\n"); + _thread_kern_sched(ucp); } } + else { + SIG_SET_ACTIVE(); + thread_sig_handle_special(sig); + SIG_SET_INACTIVE(); + } } +static void +thread_sig_savecontext(pthread_t pthread, ucontext_t *ucp) +{ + struct pthread_signal_frame *psf; + + psf = _thread_run->curframe; + + memcpy(&psf->ctx.uc, ucp, sizeof(*ucp)); + + /* XXX - Save FP registers too? */ + FP_SAVE_UC(&psf->ctx.uc); + + /* Mark the context saved as a ucontext: */ + psf->ctxtype = CTX_UC; +} + +/* + * Find a thread that can handle the signal. + */ pthread_t -_thread_sig_handle(int sig, ucontext_t * scp) +thread_sig_find(int sig) { - int i, handler_installed; + int handler_installed; pthread_t pthread, pthread_next; pthread_t suspended_thread, signaled_thread; + DBG_MSG("Looking for thread to handle signal %d\n", sig); /* Check if the signal requires a dump of thread information: */ if (sig == SIGINFO) /* Dump thread information to file: */ @@ -249,77 +287,22 @@ _thread_sig_handle(int sig, ucontext_t * scp) * This shouldn't ever occur (should this panic?). */ } else { - /* Check if a child has terminated: */ - if (sig == SIGCHLD) { - /* - * Go through the file list and set all files - * to non-blocking again in case the child - * set some of them to block. Sigh. - */ - for (i = 0; i < _thread_dtablesize; i++) { - /* Check if this file is used: */ - if (_thread_fd_table[i] != NULL) { - /* - * Set the file descriptor to - * non-blocking: - */ - _thread_sys_fcntl(i, F_SETFL, - _thread_fd_table[i]->flags | - O_NONBLOCK); - } - } - /* - * Enter a loop to wake up all threads waiting - * for a process to complete: - */ - for (pthread = TAILQ_FIRST(&_waitingq); - pthread != NULL; pthread = pthread_next) { - /* - * Grab the next thread before possibly - * destroying the link entry: - */ - pthread_next = TAILQ_NEXT(pthread, pqe); - - /* - * If this thread is waiting for a child - * process to complete, wake it up: - */ - if (pthread->state == PS_WAIT_WAIT) { - /* Make the thread runnable: */ - PTHREAD_NEW_STATE(pthread,PS_RUNNING); - - /* Return the signal number: */ - pthread->signo = sig; - } - } - } - - /* - * POSIX says that pending SIGCONT signals are - * discarded when one of these signals occurs. - */ - if (sig == SIGTSTP || sig == SIGTTIN || sig == SIGTTOU) { - /* - * Enter a loop to discard pending SIGCONT - * signals: - */ - TAILQ_FOREACH(pthread, &_thread_list, tle) { - sigdelset(&pthread->sigpend,SIGCONT); - } - } - /* * Enter a loop to look for threads that have the signal * unmasked. POSIX specifies that a thread in a sigwait * will get the signal over any other threads. Second - * preference will be threads in in a sigsuspend. If - * none of the above, then the signal is delivered to the - * first thread we find. Note that if a custom handler - * is not installed, the signal only affects threads in - * sigwait. + * preference will be threads in in a sigsuspend. Third + * preference will be the current thread. If none of the + * above, then the signal is delivered to the first thread + * that is found. Note that if a custom handler is not + * installed, the signal only affects threads in sigwait. */ suspended_thread = NULL; - signaled_thread = NULL; + if ((_thread_run != &_thread_kern_thread) && + !sigismember(&_thread_run->sigmask, sig)) + signaled_thread = _thread_run; + else + signaled_thread = NULL; if ((_thread_sigact[sig - 1].sa_handler == SIG_IGN) || (_thread_sigact[sig - 1].sa_handler == SIG_DFL)) handler_installed = 0; @@ -338,6 +321,13 @@ _thread_sig_handle(int sig, ucontext_t * scp) sigismember(pthread->data.sigwait, sig)) { /* Change the state of the thread to run: */ PTHREAD_NEW_STATE(pthread,PS_RUNNING); + /* + * A signal handler is not invoked for threads + * in sigwait. Clear the blocked and pending + * flags. + */ + _thread_sigq[sig - 1].blocked = 0; + _thread_sigq[sig - 1].pending = 0; /* Return the signal number: */ pthread->signo = sig; @@ -349,7 +339,8 @@ _thread_sig_handle(int sig, ucontext_t * scp) * we find. * * Do not attempt to deliver this signal - * to other threads. + * to other threads and do not add the signal + * to the process pending set. */ return (NULL); } @@ -367,7 +358,14 @@ _thread_sig_handle(int sig, ucontext_t * scp) * Only perform wakeups and signal delivery if there is a * custom handler installed: */ - if (handler_installed != 0) { + if (handler_installed == 0) { + /* + * There is no handler installed. Unblock the + * signal so that if a handler _is_ installed, any + * subsequent signals can be handled. + */ + _thread_sigq[sig - 1].blocked = 0; + } else { /* * If we didn't find a thread in the waiting queue, * check the all threads queue: @@ -403,12 +401,6 @@ _thread_sig_handle(int sig, ucontext_t * scp) pthread = suspended_thread; else pthread = signaled_thread; - - /* - * Perform any state changes due to signal - * arrival: - */ - thread_sig_check_state(pthread, sig); return (pthread); } } @@ -418,100 +410,166 @@ _thread_sig_handle(int sig, ucontext_t * scp) return (NULL); } -static void -thread_sig_finish_longjmp(void *arg) +void +_thread_sig_check_pending(pthread_t pthread) { + sigset_t sigset; + int i; + /* - * Check to see if the current thread performed a [_]longjmp() out of a - * signal handler. + * Check if there are pending signals for the running + * thread or process that aren't blocked: */ - if ((_thread_run->jmpflags & (JMPFLAGS_LONGJMP | JMPFLAGS__LONGJMP)) - != 0) { - _thread_run->jmpflags = JMPFLAGS_NONE; - _thread_run->continuation = NULL; - __longjmp(_thread_run->nested_jmp.jmp, - _thread_run->longjmp_val); + sigset = pthread->sigpend; + SIGSETOR(sigset, _process_sigpending); + SIGSETNAND(sigset, pthread->sigmask); + if (SIGNOTEMPTY(sigset)) { + for (i = 1; i < NSIG; i++) { + if (sigismember(&sigset, i) != 0) { + if (sigismember(&pthread->sigpend, i) != 0) + thread_sig_add(pthread, i, + /*has_args*/ 0); + else { + thread_sig_add(pthread, i, + /*has_args*/ 1); + sigdelset(&_process_sigpending, i); + } + } + } } +} + +/* + * This can only be called from the kernel scheduler. It assumes that + * all thread contexts are saved and that a signal frame can safely be + * added to any user thread. + */ +void +_thread_sig_handle_pending(void) +{ + pthread_t pthread; + int i, sig; + + PTHREAD_ASSERT(_thread_kern_in_sched != 0, + "_thread_sig_handle_pending called from outside kernel schedule"); /* - * Check to see if the current thread performed a siglongjmp - * out of a signal handler: + * Check the array of pending signals: */ - else if ((_thread_run->jmpflags & JMPFLAGS_SIGLONGJMP) != 0) { - _thread_run->jmpflags = JMPFLAGS_NONE; - _thread_run->continuation = NULL; - __siglongjmp(_thread_run->nested_jmp.sigjmp, - _thread_run->longjmp_val); + for (i = 0; i < NSIG; i++) { + if (_thread_sigq[i].pending != 0) { + /* This signal is no longer pending. */ + _thread_sigq[i].pending = 0; + + sig = _thread_sigq[i].signo; + + /* Some signals need special handling: */ + thread_sig_handle_special(sig); + + if (_thread_sigq[i].blocked == 0) { + /* + * Block future signals until this one + * is handled: + */ + _thread_sigq[i].blocked = 1; + + if ((pthread = thread_sig_find(sig)) != NULL) { + /* + * Setup the target thread to receive + * the signal: + */ + thread_sig_add(pthread, sig, + /*has_args*/ 1); + } + } + } } } static void -handle_state_change(pthread_t pthread) +thread_sig_handle_special(int sig) { - /* - * We should only need to handle threads whose state was - * changed to running: - */ - if (pthread->state == PS_RUNNING) { - switch (pthread->oldstate) { + pthread_t pthread, pthread_next; + int i; + + switch (sig) { + case SIGCHLD: /* - * States which do not change when a signal is trapped: + * Go through the file list and set all files + * to non-blocking again in case the child + * set some of them to block. Sigh. */ - case PS_DEAD: - case PS_DEADLOCK: - case PS_RUNNING: - case PS_SIGTHREAD: - case PS_STATE_MAX: - case PS_SUSPENDED: - break; - + for (i = 0; i < _thread_dtablesize; i++) { + /* Check if this file is used: */ + if (_thread_fd_table[i] != NULL) { + /* + * Set the file descriptor to non-blocking: + */ + _thread_sys_fcntl(i, F_SETFL, + _thread_fd_table[i]->flags | O_NONBLOCK); + } + } /* - * States which need to return to critical sections - * before they can switch contexts: + * Enter a loop to wake up all threads waiting + * for a process to complete: */ - case PS_COND_WAIT: - case PS_FDLR_WAIT: - case PS_FDLW_WAIT: - case PS_FILE_WAIT: - case PS_JOIN: - case PS_MUTEX_WAIT: - /* Indicate that the thread was interrupted: */ - pthread->interrupted = 1; + for (pthread = TAILQ_FIRST(&_waitingq); + pthread != NULL; pthread = pthread_next) { + /* + * Grab the next thread before possibly + * destroying the link entry: + */ + pthread_next = TAILQ_NEXT(pthread, pqe); + /* - * Defer the [sig|_]longjmp until leaving the critical - * region: + * If this thread is waiting for a child + * process to complete, wake it up: */ - pthread->jmpflags |= JMPFLAGS_DEFERRED; - - /* Set the continuation routine: */ - pthread->continuation = thread_sig_finish_longjmp; - /* FALLTHROUGH */ - case PS_FDR_WAIT: - case PS_FDW_WAIT: - case PS_POLL_WAIT: - case PS_SELECT_WAIT: - case PS_SIGSUSPEND: - case PS_SIGWAIT: - case PS_SLEEP_WAIT: - case PS_SPINBLOCK: - case PS_WAIT_WAIT: - if ((pthread->flags & PTHREAD_FLAGS_IN_WAITQ) != 0) { - PTHREAD_WAITQ_REMOVE(pthread); - if (pthread->flags & PTHREAD_FLAGS_IN_WORKQ) - PTHREAD_WORKQ_REMOVE(pthread); + if (pthread->state == PS_WAIT_WAIT) { + /* Make the thread runnable: */ + PTHREAD_NEW_STATE(pthread,PS_RUNNING); + + /* Return the signal number: */ + pthread->signo = sig; } - break; } + break; - if ((pthread->flags & PTHREAD_FLAGS_IN_PRIOQ) == 0) - PTHREAD_PRIOQ_INSERT_TAIL(pthread); + /* + * POSIX says that pending SIGCONT signals are + * discarded when one of these signals occurs. + */ + case SIGTSTP: + case SIGTTIN: + case SIGTTOU: + /* + * Enter a loop to discard pending SIGCONT + * signals: + */ + TAILQ_FOREACH(pthread, &_thread_list, tle) { + sigdelset(&pthread->sigpend, SIGCONT); + } + break; + + default: + break; } } - -/* Perform thread specific actions in response to a signal: */ +/* + * Perform thread specific actions in response to a signal. + * This function is only called if there is a handler installed + * for the signal, and if the target thread has the signal + * unmasked. + */ static void -thread_sig_check_state(pthread_t pthread, int sig) +thread_sig_add(pthread_t pthread, int sig, int has_args) { + int restart, frame; + int block_signals = 0; + int suppress_handler = 0; + + restart = _thread_sigact[sig - 1].sa_flags & SA_RESTART; + /* * Process according to thread state: */ @@ -519,32 +577,54 @@ thread_sig_check_state(pthread_t pthread, int sig) /* * States which do not change when a signal is trapped: */ - case PS_COND_WAIT: case PS_DEAD: case PS_DEADLOCK: - case PS_FILE_WAIT: - case PS_JOIN: - case PS_MUTEX_WAIT: - case PS_RUNNING: case PS_STATE_MAX: case PS_SIGTHREAD: - case PS_SPINBLOCK: + /* + * You can't call a signal handler for threads in these + * states. + */ + suppress_handler = 1; + break; + + /* + * States which do not need any cleanup handling when signals + * occur: + */ + case PS_RUNNING: + /* + * Remove the thread from the queue before changing its + * priority: + */ + if ((pthread->flags & PTHREAD_FLAGS_IN_PRIOQ) != 0) + PTHREAD_PRIOQ_REMOVE(pthread); + break; + case PS_SUSPENDED: - /* Increment the pending signal count. */ - sigaddset(&pthread->sigpend,sig); + break; + + case PS_SPINBLOCK: + /* Remove the thread from the workq and waitq: */ + PTHREAD_WORKQ_REMOVE(pthread); + PTHREAD_WAITQ_REMOVE(pthread); + /* Make the thread runnable: */ + PTHREAD_SET_STATE(pthread, PS_RUNNING); break; case PS_SIGWAIT: + /* The signal handler is not called for threads in SIGWAIT. */ + suppress_handler = 1; /* Wake up the thread if the signal is blocked. */ if (sigismember(pthread->data.sigwait, sig)) { /* Change the state of the thread to run: */ - PTHREAD_NEW_STATE(pthread,PS_RUNNING); + PTHREAD_NEW_STATE(pthread, PS_RUNNING); /* Return the signal number: */ pthread->signo = sig; } else /* Increment the pending signal count. */ - sigaddset(&pthread->sigpend,sig); + sigaddset(&pthread->sigpend, sig); break; /* @@ -552,64 +632,142 @@ thread_sig_check_state(pthread_t pthread, int sig) * SIGCHLD signals. */ case PS_WAIT_WAIT: - /* - * Check for signals other than the death of a child - * process: - */ - if (sig != SIGCHLD) - /* Flag the operation as interrupted: */ - pthread->interrupted = 1; + if (sig == SIGCHLD) { + /* Change the state of the thread to run: */ + PTHREAD_WAITQ_REMOVE(pthread); + PTHREAD_SET_STATE(pthread, PS_RUNNING); - /* Change the state of the thread to run: */ - PTHREAD_NEW_STATE(pthread,PS_RUNNING); + /* Return the signal number: */ + pthread->signo = sig; + } + else { + /* + * Mark the thread as interrupted only if the + * restart flag is not set on the signal action: + */ + if (restart == 0) + pthread->interrupted = 1; + PTHREAD_WAITQ_REMOVE(pthread); + PTHREAD_SET_STATE(pthread, PS_RUNNING); + } + break; - /* Return the signal number: */ - pthread->signo = sig; + /* + * States which cannot be interrupted but still require the + * signal handler to run: + */ + case PS_COND_WAIT: + case PS_JOIN: + case PS_MUTEX_WAIT: + /* + * Remove the thread from the wait queue. It will + * be added back to the wait queue once all signal + * handlers have been invoked. + */ + PTHREAD_WAITQ_REMOVE(pthread); break; /* - * States that are interrupted by the occurrence of a signal - * other than the scheduling alarm: + * States which are interruptible but may need to be removed + * from queues before any signal handler is called. + * + * XXX - We may not need to handle this condition, but will + * mark it as a potential problem. */ case PS_FDLR_WAIT: case PS_FDLW_WAIT: + case PS_FILE_WAIT: + if (restart == 0) + pthread->interrupted = 1; + /* + * Remove the thread from the wait queue. Our + * signal handler hook will remove this thread + * from the fd or file queue before invoking + * the actual handler. + */ + PTHREAD_WAITQ_REMOVE(pthread); + /* + * To ensure the thread is removed from the fd and file + * queues before any other signal interrupts it, set the + * signal mask to block all signals. As soon as the thread + * is removed from the queue the signal mask will be + * restored. + */ + block_signals = 1; + break; + + /* + * States which are interruptible: + */ case PS_FDR_WAIT: case PS_FDW_WAIT: - case PS_POLL_WAIT: - case PS_SLEEP_WAIT: - case PS_SELECT_WAIT: - if ((_thread_sigact[sig - 1].sa_flags & SA_RESTART) == 0) { - /* Flag the operation as interrupted: */ + if (restart == 0) { + /* + * Flag the operation as interrupted and + * set the state to running: + */ pthread->interrupted = 1; - - if (pthread->flags & PTHREAD_FLAGS_IN_WORKQ) - PTHREAD_WORKQ_REMOVE(pthread); - - /* Change the state of the thread to run: */ - PTHREAD_NEW_STATE(pthread,PS_RUNNING); - - /* Return the signal number: */ - pthread->signo = sig; + PTHREAD_SET_STATE(pthread, PS_RUNNING); } + PTHREAD_WORKQ_REMOVE(pthread); + PTHREAD_WAITQ_REMOVE(pthread); break; - case PS_SIGSUSPEND: + case PS_POLL_WAIT: + case PS_SELECT_WAIT: + case PS_SLEEP_WAIT: /* - * Only wake up the thread if there is a handler installed - * for the signal. + * Unmasked signals always cause poll, select, and sleep + * to terminate early, regardless of SA_RESTART: */ - if (_thread_sigact[sig - 1].sa_handler != SIG_DFL) { - /* Change the state of the thread to run: */ - PTHREAD_NEW_STATE(pthread,PS_RUNNING); + pthread->interrupted = 1; + /* Remove threads in poll and select from the workq: */ + if ((pthread->flags & PTHREAD_FLAGS_IN_WORKQ) != 0) + PTHREAD_WORKQ_REMOVE(pthread); + PTHREAD_WAITQ_REMOVE(pthread); + PTHREAD_SET_STATE(pthread, PS_RUNNING); + break; - /* Return the signal number: */ - pthread->signo = sig; - } + case PS_SIGSUSPEND: + PTHREAD_WAITQ_REMOVE(pthread); + PTHREAD_SET_STATE(pthread, PS_RUNNING); break; } + + if (suppress_handler == 0) { + /* + * Save the current state of the thread and add a + * new signal frame. + */ + frame = pthread->sigframe_count; + thread_sigframe_save(pthread, pthread->curframe); + thread_sigframe_add(pthread, sig); + pthread->sigframes[frame + 1]->sig_has_args = has_args; + SIGSETOR(pthread->sigmask, _thread_sigact[sig - 1].sa_mask); + if (block_signals != 0) { + /* Save the signal mask and block all signals: */ + pthread->sigframes[frame + 1]->saved_state.psd_sigmask = + pthread->sigmask; + sigfillset(&pthread->sigmask); + } + + /* Make sure the thread is runnable: */ + if (pthread->state != PS_RUNNING) + PTHREAD_SET_STATE(pthread, PS_RUNNING); + /* + * The thread should be removed from all scheduling + * queues at this point. Raise the priority and place + * the thread in the run queue. + */ + pthread->active_priority |= PTHREAD_SIGNAL_PRIORITY; + if (pthread != _thread_run) + PTHREAD_PRIOQ_INSERT_TAIL(pthread); + } } -/* Send a signal to a specific thread (ala pthread_kill): */ +/* + * Send a signal to a specific thread (ala pthread_kill): + */ void _thread_sig_send(pthread_t pthread, int sig) { @@ -620,142 +778,400 @@ _thread_sig_send(pthread_t pthread, int sig) if (pthread->state == PS_SIGWAIT && sigismember(pthread->data.sigwait, sig)) { /* Change the state of the thread to run: */ - PTHREAD_NEW_STATE(pthread,PS_RUNNING); + PTHREAD_NEW_STATE(pthread, PS_RUNNING); /* Return the signal number: */ pthread->signo = sig; - } else if (pthread->state != PS_SIGWAIT && - !sigismember(&pthread->sigmask, sig)) { - /* Perform any state changes due to signal arrival: */ - thread_sig_check_state(pthread, sig); - /* Increment the pending signal count. */ - sigaddset(&pthread->sigpend,sig); + } else if (pthread == _thread_run) { + /* Add the signal to the pending set: */ + sigaddset(&pthread->sigpend, sig); + /* + * Deliver the signal to the process if a + * handler is not installed: + */ + if (_thread_sigact[sig - 1].sa_handler == SIG_DFL) + kill(getpid(), sig); + if (!sigismember(&pthread->sigmask, sig)) { + /* + * Call the kernel scheduler which will safely + * install a signal frame for this thread: + */ + _thread_kern_sched_sig(); + } } else { - /* Increment the pending signal count. */ - sigaddset(&pthread->sigpend,sig); + if (pthread->state != PS_SIGWAIT && + !sigismember(&pthread->sigmask, sig)) { + /* Protect the scheduling queues: */ + _thread_kern_sig_defer(); + /* + * Perform any state changes due to signal + * arrival: + */ + thread_sig_add(pthread, sig, /* has args */ 0); + /* Unprotect the scheduling queues: */ + _thread_kern_sig_undefer(); + } + else + /* Increment the pending signal count. */ + sigaddset(&pthread->sigpend,sig); + + /* + * Deliver the signal to the process if a + * handler is not installed: + */ + if (_thread_sigact[sig - 1].sa_handler == SIG_DFL) + kill(getpid(), sig); } } } -/* Dispatch pending signals to the running thread: */ +/* + * User thread signal handler wrapper. + * + * thread - current running thread + */ void -_dispatch_signals() +_thread_sig_wrapper(void) { - sigset_t sigset; - int i; + void (*sigfunc)(int, siginfo_t *, void *); + struct pthread_signal_frame *psf; + pthread_t thread; + int dead = 0; + int i, sig, has_args; + int frame, dst_frame; + + thread = _thread_run; + + /* Get the current frame and state: */ + frame = thread->sigframe_count; + PTHREAD_ASSERT(frame > 0, "Invalid signal frame in signal handler"); + psf = thread->curframe; + + /* Check the threads previous state: */ + if (psf->saved_state.psd_state != PS_RUNNING) { + /* + * Do a little cleanup handling for those threads in + * queues before calling the signal handler. Signals + * for these threads are temporarily blocked until + * after cleanup handling. + */ + switch (psf->saved_state.psd_state) { + case PS_FDLR_WAIT: + case PS_FDLW_WAIT: + _fd_lock_backout(thread); + psf->saved_state.psd_state = PS_RUNNING; + /* Reenable signals: */ + thread->sigmask = psf->saved_state.psd_sigmask; + break; + + case PS_FILE_WAIT: + _flockfile_backout(thread); + psf->saved_state.psd_state = PS_RUNNING; + /* Reenable signals: */ + thread->sigmask = psf->saved_state.psd_sigmask; + break; + + default: + break; + } + } /* - * Check if there are pending signals for the running - * thread or process that aren't blocked: + * Unless the thread exits or longjmps out of the signal handler, + * return to the previous frame: */ - sigset = _thread_run->sigpend; - SIGSETOR(sigset, _process_sigpending); - SIGSETNAND(sigset, _thread_run->sigmask); - if (SIGNOTEMPTY(sigset)) { + dst_frame = frame - 1; + + /* + * Check that a custom handler is installed and if the signal + * is not blocked: + */ + sigfunc = _thread_sigact[psf->signo - 1].sa_sigaction; + if (((__sighandler_t *)sigfunc != SIG_DFL) && + ((__sighandler_t *)sigfunc != SIG_IGN)) { /* - * Enter a loop to calculate deliverable pending signals - * before actually delivering them. The pending signals - * must be removed from the pending signal sets before - * calling the signal handler because the handler may - * call library routines that again check for and deliver - * pending signals. + * The signal jump buffer is allocated off the stack. + * If the signal handler tries to [_][sig]longjmp() or + * setcontext(), our wrapped versions of these routines + * will copy the user supplied jump buffer or context + * to the destination signal frame, set the destination + * signal frame in psf->dst_frame, and _longjmp() back + * to here. */ - for (i = 1; i < NSIG; i++) { + jmp_buf jb; + + /* + * Set up the context for abnormal returns out of signal + * handlers. + */ + psf->sig_jb = &jb; + if (_setjmp(jb) == 0) { + DBG_MSG("_thread_sig_wrapper: Entering frame %d, " + "stack 0x%lx\n", frame, GET_STACK_JB(jb)); /* - * Check that a custom handler is installed - * and if the signal is not blocked: + * Invalidate the destination frame before calling + * the signal handler. */ - if (_thread_sigact[i - 1].sa_handler != SIG_DFL && - _thread_sigact[i - 1].sa_handler != SIG_IGN && - sigismember(&sigset, i)) { - if (sigismember(&_thread_run->sigpend,i)) - /* Clear the thread pending signal: */ - sigdelset(&_thread_run->sigpend,i); - else - /* Clear the process pending signal: */ - sigdelset(&_process_sigpending,i); - } + psf->dst_frame = -1; + + /* + * Dispatch the signal via the custom signal + * handler: + */ + if (psf->sig_has_args == 0) + (*(sigfunc))(psf->signo, NULL, NULL); + else if ((_thread_sigact[psf->signo - 1].sa_flags & + SA_SIGINFO) != 0) + (*(sigfunc))(psf->signo, + &_thread_sigq[psf->signo - 1].siginfo, + &_thread_sigq[psf->signo - 1].uc); else - /* Remove the signal if it can't be handled: */ - sigdelset(&sigset, i); + (*(sigfunc))(psf->signo, + (siginfo_t *)_thread_sigq[psf->signo - 1].siginfo.si_code, + &_thread_sigq[psf->signo - 1].uc); } + else { + /* + * The return from _setjmp() should only be non-zero + * when the signal handler wants to xxxlongjmp() or + * setcontext() to a different context, or if the + * thread has exited (via pthread_exit). + */ + /* + * Grab a copy of the destination frame before it + * gets clobbered after unwinding. + */ + dst_frame = psf->dst_frame; + DBG_MSG("Abnormal exit from handler for signal %d, " + "frame %d\n", psf->signo, frame); + + /* Has the thread exited? */ + if ((dead = thread->flags & PTHREAD_EXITING) != 0) + /* When exiting, unwind to frame 0. */ + dst_frame = 0; + else if ((dst_frame < 0) || (dst_frame > frame)) + PANIC("Attempt to unwind to invalid " + "signal frame"); + + /* Unwind to the target frame: */ + for (i = frame; i > dst_frame; i--) { + DBG_MSG("Leaving frame %d, signal %d\n", i, + thread->sigframes[i]->signo); + /* Leave the current signal frame: */ + thread_sigframe_leave(thread, i); - /* Now deliver the signals: */ - for (i = 1; i < NSIG; i++) { - if (sigismember(&sigset, i)) - /* Deliver the signal to the running thread: */ - _thread_sig_deliver(_thread_run, i); + /* + * Save whatever is needed out of the state + * data; as soon as the frame count is + * is decremented, another signal can arrive + * and corrupt this view of the state data. + */ + sig = thread->sigframes[i]->signo; + has_args = thread->sigframes[i]->sig_has_args; + + /* + * We're done with this signal frame: + */ + thread->curframe = thread->sigframes[i - 1]; + thread->sigframe_count = i - 1; + + /* + * Only unblock the signal if it was a + * process signal as opposed to a signal + * generated by pthread_kill(). + */ + if (has_args != 0) + _thread_sigq[sig - 1].blocked = 0; + } } } + + /* + * Call the kernel scheduler to schedule the next + * thread. + */ + if (dead == 0) { + /* Restore the threads state: */ + thread_sigframe_restore(thread, thread->sigframes[dst_frame]); + _thread_kern_sched_frame(dst_frame); + } + else { + PTHREAD_ASSERT(dst_frame == 0, + "Invalid signal frame for dead thread"); + + /* Perform any necessary cleanup before exiting. */ + thread_sigframe_leave(thread, 0); + + /* This should never return: */ + _thread_exit_finish(); + PANIC("Return from _thread_exit_finish in signal wrapper"); + } } -/* Deliver a signal to a thread: */ -void -_thread_sig_deliver(pthread_t pthread, int sig) +static void +thread_sigframe_add(pthread_t thread, int sig) { - sigset_t mask; - pthread_t pthread_saved; - jmp_buf jb, *saved_sighandler_jmp_buf; + unsigned long stackp = 0; + + /* Get the top of the threads stack: */ + switch (thread->curframe->ctxtype) { + case CTX_JB: + case CTX_JB_NOSIG: + stackp = GET_STACK_JB(thread->curframe->ctx.jb); + break; + case CTX_SJB: + stackp = GET_STACK_SJB(thread->curframe->ctx.sigjb); + break; + case CTX_UC: + stackp = GET_STACK_UC(&thread->curframe->ctx.uc); + break; + default: + PANIC("Invalid thread context type"); + break; + } /* - * Check that a custom handler is installed - * and if the signal is not blocked: + * Leave a little space on the stack and round down to the + * nearest aligned word: */ - if (_thread_sigact[sig - 1].sa_handler != SIG_DFL && - _thread_sigact[sig - 1].sa_handler != SIG_IGN) { - /* Save the current thread: */ - pthread_saved = _thread_run; + stackp -= sizeof(double); + stackp &= ~0x3UL; + + /* Allocate room on top of the stack for a new signal frame: */ + stackp -= sizeof(struct pthread_signal_frame); + + /* Set up the new frame: */ + thread->sigframe_count++; + thread->sigframes[thread->sigframe_count] = + (struct pthread_signal_frame *) stackp; + thread->curframe = thread->sigframes[thread->sigframe_count]; + thread->curframe->stackp = stackp; + thread->curframe->ctxtype = CTX_JB; + thread->curframe->longjmp_val = 1; + thread->curframe->signo = sig; - /* Save the threads signal mask: */ - mask = pthread->sigmask; - - /* - * Add the current signal and signal handler - * mask to the thread's current signal mask: - */ - SIGSETOR(pthread->sigmask, _thread_sigact[sig - 1].sa_mask); - sigaddset(&pthread->sigmask, sig); + /* + * Set up the context: + */ + _setjmp(thread->curframe->ctx.jb); + SET_STACK_JB(thread->curframe->ctx.jb, stackp); + SET_RETURN_ADDR_JB(thread->curframe->ctx.jb, _thread_sig_wrapper); +} - /* Current thread inside critical region? */ - if (_thread_run->sig_defer_count > 0) - pthread->sig_defer_count++; +/* + * Locate the signal frame from the specified stack pointer. + */ +int +_thread_sigframe_find(pthread_t pthread, void *stackp) +{ + int frame; - /* Increment the number of nested signals being handled. */ - pthread->signal_nest_level++; + /* + * Find the destination of the target frame based on the + * given stack pointer. + */ + for (frame = pthread->sigframe_count; frame >= 0; frame--) { + if (stackp < (void *)pthread->sigframes[frame]->stackp) + break; + } + return (frame); +} + +void +thread_sigframe_leave(pthread_t thread, int frame) +{ + struct pthread_state_data *psd; - /* - * The jump buffer is allocated off the stack and the current - * jump buffer is saved. If the signal handler tries to - * [sig|_]longjmp(), our version of [sig|_]longjmp() will copy - * the user supplied jump buffer into - * _thread_run->nested_jmp.[sig]jmp and _longjmp() back to here. - */ - saved_sighandler_jmp_buf = pthread->sighandler_jmp_buf; - pthread->sighandler_jmp_buf = &jb; + psd = &thread->sigframes[frame]->saved_state; - _thread_run = pthread; + /* + * Perform any necessary cleanup for this signal frame: + */ + switch (psd->psd_state) { + case PS_DEAD: + case PS_DEADLOCK: + case PS_RUNNING: + case PS_SIGTHREAD: + case PS_STATE_MAX: + case PS_SUSPENDED: + break; - if (_setjmp(jb) == 0) { - /* - * Dispatch the signal via the custom signal - * handler: - */ - (*(_thread_sigact[sig - 1].sa_handler))(sig); - } + /* + * Threads in the following states need to be removed + * from queues. + */ + case PS_COND_WAIT: + _cond_wait_backout(thread); + if ((psd->psd_flags & PTHREAD_FLAGS_IN_WAITQ) != 0) + PTHREAD_WAITQ_REMOVE(thread); + break; - _thread_run = pthread_saved; + case PS_FDLR_WAIT: + case PS_FDLW_WAIT: + _fd_lock_backout(thread); + if ((psd->psd_flags & PTHREAD_FLAGS_IN_WAITQ) != 0) + PTHREAD_WAITQ_REMOVE(thread); + break; - pthread->sighandler_jmp_buf = saved_sighandler_jmp_buf; + case PS_FILE_WAIT: + _flockfile_backout(thread); + if ((psd->psd_flags & PTHREAD_FLAGS_IN_WAITQ) != 0) + PTHREAD_WAITQ_REMOVE(thread); + break; - /* Decrement the signal nest level. */ - pthread->signal_nest_level--; + case PS_JOIN: + _join_backout(thread); + if ((psd->psd_flags & PTHREAD_FLAGS_IN_WAITQ) != 0) + PTHREAD_WAITQ_REMOVE(thread); + break; - /* Current thread inside critical region? */ - if (_thread_run->sig_defer_count > 0) - pthread->sig_defer_count--; + case PS_MUTEX_WAIT: + _mutex_lock_backout(thread); + if ((psd->psd_flags & PTHREAD_FLAGS_IN_WAITQ) != 0) + PTHREAD_WAITQ_REMOVE(thread); + break; - /* Restore the threads signal mask: */ - pthread->sigmask = mask; + case PS_FDR_WAIT: + case PS_FDW_WAIT: + case PS_POLL_WAIT: + case PS_SELECT_WAIT: + case PS_SIGSUSPEND: + case PS_SIGWAIT: + case PS_SLEEP_WAIT: + case PS_SPINBLOCK: + case PS_WAIT_WAIT: + if ((psd->psd_flags & PTHREAD_FLAGS_IN_WAITQ) != 0) { + PTHREAD_WAITQ_REMOVE(thread); + if ((psd->psd_flags & PTHREAD_FLAGS_IN_WORKQ) != 0) + PTHREAD_WORKQ_REMOVE(thread); + } + break; } } + +static void +thread_sigframe_restore(pthread_t thread, struct pthread_signal_frame *psf) +{ + thread->interrupted = psf->saved_state.psd_interrupted; + thread->sigmask = psf->saved_state.psd_sigmask; + thread->state = psf->saved_state.psd_state; + thread->flags = psf->saved_state.psd_flags; + thread->wakeup_time = psf->saved_state.psd_wakeup_time; + thread->data = psf->saved_state.psd_wait_data; +} + +static void +thread_sigframe_save(pthread_t thread, struct pthread_signal_frame *psf) +{ + psf->saved_state.psd_interrupted = thread->interrupted; + psf->saved_state.psd_sigmask = thread->sigmask; + psf->saved_state.psd_state = thread->state; + psf->saved_state.psd_flags = thread->flags; + thread->flags &= PTHREAD_FLAGS_PRIVATE | PTHREAD_FLAGS_TRACE | + PTHREAD_FLAGS_IN_CONDQ | PTHREAD_FLAGS_IN_MUTEXQ | + PTHREAD_FLAGS_IN_JOINQ; + psf->saved_state.psd_wakeup_time = thread->wakeup_time; + psf->saved_state.psd_wait_data = thread->data; +} + #endif |