diff options
Diffstat (limited to 'mit-pthreads/pthreads/signal.c')
-rw-r--r-- | mit-pthreads/pthreads/signal.c | 653 |
1 files changed, 653 insertions, 0 deletions
diff --git a/mit-pthreads/pthreads/signal.c b/mit-pthreads/pthreads/signal.c new file mode 100644 index 00000000000..7da4183c1cb --- /dev/null +++ b/mit-pthreads/pthreads/signal.c @@ -0,0 +1,653 @@ +/* ==== signal.c ============================================================ + * Copyright (c) 1993, 1994 by Chris Provenzano, proven@mit.edu + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Chris Provenzano. + * 4. The name of Chris Provenzano may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY CHRIS PROVENZANO ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CHRIS PROVENZANO BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Description : Queue functions. + * + * 1.00 93/07/21 proven + * -Started coding this file. + */ + +#ifndef lint +static const char rcsid[] = "$Id$"; +#endif + +#include <config.h> +#include <pthread.h> +#include <signal.h> + +/* This will force init.o to get dragged in; if you've got support for + C++ initialization, that'll cause pthread_init to be called at + program startup automatically, so the application won't need to + call it explicitly. */ + +extern char __pthread_init_hack; +char *__pthread_init_hack_2 = &__pthread_init_hack; + +/* + * Time which select in fd_kern_wait() will sleep. + * If there are no threads to run we sleep for an hour or until + * we get an interrupt or an fd thats awakens. To make sure we + * don't miss an interrupt this variable gets reset too zero in + * sig_handler_real(). + */ +struct timeval __fd_kern_wait_timeout = { 0, 0 }; + +/* + * Global for user-kernel lock, and blocked signals + */ + +static sig_atomic_t signum_to_process[SIGMAX + 1] = { 0, }; +volatile sig_atomic_t sig_to_process = 0; + +/* static volatile sigset_t sig_to_process; */ +static volatile int sig_count = 0; + +static void sig_handler(int signal); +static void set_thread_timer(); +static void __cleanup_after_resume( void ); +void sig_prevent(void); +void sig_resume(void); + +/* ========================================================================== + * context_switch() + * + * This routine saves the current state of the running thread gets + * the next thread to run and restores it's state. To allow different + * processors to work with this routine, I allow the machdep_restore_state() + * to either return or have it return from machdep_save_state with a value + * other than 0, this is for implementations which use setjmp/longjmp. + */ +static void context_switch() +{ + struct pthread **current, *next, *last, **dead; + + if (pthread_run->state == PS_RUNNING) { + /* Put current thread back on the queue */ + pthread_prio_queue_enq(pthread_current_prio_queue, pthread_run); + } + + /* save floating point registers if necessary */ + if (!(pthread_run->attr.flags & PTHREAD_NOFLOAT)) { + machdep_save_float_state(pthread_run); + } + /* save state of current thread */ + if (machdep_save_state()) { + return; + } + + last = pthread_run; + + /* Poll all fds */ + fd_kern_poll(); + +context_switch_reschedule:; + /* Are there any threads to run */ + if (pthread_run = pthread_prio_queue_deq(pthread_current_prio_queue)) { + /* restore floating point registers if necessary */ + if (!(pthread_run->attr.flags & PTHREAD_NOFLOAT)) { + machdep_restore_float_state(); + } + uthread_sigmask = &(pthread_run->sigmask); + /* restore state of new current thread */ + machdep_restore_state(); + return; + } + + /* Are there any threads at all */ + for (next = pthread_link_list; next; next = next->pll) { + if ((next->state != PS_UNALLOCED) && (next->state != PS_DEAD)) { + sigset_t sig_to_block, oset; + + sigfillset(&sig_to_block); + + /* + * Check sig_to_process before calling fd_kern_wait, to handle + * things like zero timeouts to select() which would register + * a signal with the sig_handler_fake() call. + * + * This case should ignore SIGVTALRM + */ + machdep_sys_sigprocmask(SIG_BLOCK, &sig_to_block, &oset); + signum_to_process[SIGVTALRM] = 0; + if (sig_to_process) { + /* Process interrupts */ + /* + * XXX pthread_run should not be set! + * Places where it dumps core should be fixed to + * check for the existance of pthread_run --proven + */ + sig_handler(0); + } else { + machdep_sys_sigprocmask(SIG_UNBLOCK, &sig_to_block, &oset); + /* + * Do a wait, timeout is set to a hour unless we get an + * intr. before the select in wich case it polls. + */ + fd_kern_wait(); + machdep_sys_sigprocmask(SIG_BLOCK, &sig_to_block, &oset); + /* Check for interrupts, but ignore SIGVTALR */ + signum_to_process[SIGVTALRM] = 0; + if (sig_to_process) { + /* Process interrupts */ + sig_handler(0); + } + } + machdep_sys_sigprocmask(SIG_UNBLOCK, &sig_to_block, &oset); + goto context_switch_reschedule; + } + } + + /* There are no threads alive. */ + pthread_run = last; + exit(0); +} + +#if !defined(HAVE_SYSCALL_SIGSUSPEND) && defined(HAVE_SYSCALL_SIGPAUSE) + +/* ========================================================================== + * machdep_sys_sigsuspend() + */ +int machdep_sys_sigsuspend(sigset_t * set) +{ + return(machdep_sys_sigpause(* set)); +} + +#endif + +/* ========================================================================== + * sig_handler_pause() + * + * Wait until a signal is sent to the process. + */ +void sig_handler_pause() +{ + sigset_t sig_to_block, sig_to_pause, oset; + + sigfillset(&sig_to_block); + sigemptyset(&sig_to_pause); + machdep_sys_sigprocmask(SIG_BLOCK, &sig_to_block, &oset); +/* if (!(SIG_ANY(sig_to_process))) { */ + if (!sig_to_process) { + machdep_sys_sigsuspend(&sig_to_pause); + } + machdep_sys_sigprocmask(SIG_UNBLOCK, &sig_to_block, &oset); +} + +/* ========================================================================== + * context_switch_done() + * + * This routine does all the things that are necessary after a context_switch() + * calls the machdep_restore_state(). DO NOT put this in the context_switch() + * routine because sometimes the machdep_restore_state() doesn't return + * to context_switch() but instead ends up in machdep_thread_start() or + * some such routine, which will need to call this routine and + * sig_check_and_resume(). + */ +void context_switch_done() +{ + /* sigdelset((sigset_t *)&sig_to_process, SIGVTALRM); */ + signum_to_process[SIGVTALRM] = 0; + set_thread_timer(); +} + +/* ========================================================================== + * set_thread_timer() + * + * Assums kernel is locked. + */ +static void set_thread_timer() +{ + static int last_sched_attr = SCHED_RR; + + switch (pthread_run->attr.schedparam_policy) { + case SCHED_RR: + machdep_set_thread_timer(&(pthread_run->machdep_data)); + break; + case SCHED_FIFO: + if (last_sched_attr != SCHED_FIFO) { + machdep_unset_thread_timer(NULL); + } + break; + case SCHED_IO: + if ((last_sched_attr != SCHED_IO) && (!sig_count)) { + machdep_set_thread_timer(&(pthread_run->machdep_data)); + } + break; + default: + machdep_set_thread_timer(&(pthread_run->machdep_data)); + break; + } + last_sched_attr = pthread_run->attr.schedparam_policy; +} + +/* ========================================================================== + * sigvtalrm() + */ +static inline void sigvtalrm() +{ + if (sig_count) { + sigset_t sigall, oset; + + sig_count = 0; + + /* Unblock all signals */ + sigemptyset(&sigall); + machdep_sys_sigprocmask(SIG_SETMASK, &sigall, &oset); + } + context_switch(); + context_switch_done(); +} + +/* ========================================================================== + * sigdefault() + */ +static inline void sigdefault(int sig) +{ + int ret; + + ret = pthread_sig_register(sig); + if (pthread_run && (ret > pthread_run->pthread_priority)) { + sigvtalrm(); + } +} + +/* ========================================================================== + * sig_handler_switch() + */ +static inline void sig_handler_switch(int sig) +{ + int ret; + + switch(sig) { + case 0: + break; + case SIGVTALRM: + sigvtalrm(); + break; + case SIGALRM: +/* sigdelset((sigset_t *)&sig_to_process, SIGALRM); */ + signum_to_process[SIGALRM] = 0; + switch (ret = sleep_wakeup()) { + default: + if (pthread_run && (ret > pthread_run->pthread_priority)) { + sigvtalrm(); + } + case 0: + break; + case NOTOK: + /* Do the registered action, no threads were sleeping */ + /* There is a timing window that gets + * here when no threads are on the + * sleep queue. This is a quick fix. + * The real problem is possibly related + * to heavy use of condition variables + * with time outs. + * (mevans) + *sigdefault(sig); + */ + break; + } + break; + case SIGCHLD: +/* sigdelset((sigset_t *)&sig_to_process, SIGCHLD); */ + signum_to_process[SIGCHLD] = 0; + switch (ret = wait_wakeup()) { + default: + if (pthread_run && (ret > pthread_run->pthread_priority)) { + sigvtalrm(); + } + case 0: + break; + case NOTOK: + /* Do the registered action, no threads were waiting */ + sigdefault(sig); + break; + } + break; + +#ifdef SIGINFO + case SIGINFO: + pthread_dump_info (); + /* Then fall through, invoking the application's + signal handler after printing our info out. + + I'm not convinced that this is right, but I'm not + 100% convinced that it is wrong, and this is how + Chris wants it done... */ +#endif + + default: + /* Do the registered action */ + if (!sigismember(uthread_sigmask, sig)) { + /* + * If the signal isn't masked by the last running thread and + * the signal behavior is default or ignore then we can + * execute it immediatly. --proven + */ + pthread_sig_default(sig); + } + signum_to_process[sig] = 0; + sigdefault(sig); + break; + } + +} + +/* ========================================================================== + * sig_handler() + * + * Process signal that just came in, plus any pending on the signal mask. + * All of these must be resolved. + * + * Assumes the kernel is locked. + */ +static void sig_handler(int sig) +{ + if (pthread_kernel_lock != 1) { + PANIC(); + } + + if (sig) { + sig_handler_switch(sig); + } + + while (sig_to_process) { + for (sig_to_process = 0, sig = 1; sig <= SIGMAX; sig++) { + if (signum_to_process[sig]) { + sig_handler_switch(sig); + } + } + } + + +/* + if (SIG_ANY(sig_to_process)) { + for (sig = 1; sig <= SIGMAX; sig++) { + if (sigismember((sigset_t *)&sig_to_process, sig)) { + goto sig_handler_top; + } + } + } +*/ +} + +/* ========================================================================== + * sig_handler_real() + * + * On a multi-processor this would need to use the test and set instruction + * otherwise the following will work. + */ +void sig_handler_real(int sig) +{ + /* + * Get around systems with BROKEN signal handlers. + * + * Some systems will reissue SIGCHLD if the handler explicitly + * clear the signal pending by either doing a wait() or + * ignoring the signal. + */ +#if defined BROKEN_SIGNALS + if (sig == SIGCHLD) { + sigignore(SIGCHLD); + signal(SIGCHLD, sig_handler_real); + } +#endif + + if (pthread_kernel_lock) { + /* sigaddset((sigset_t *)&sig_to_process, sig); */ + __fd_kern_wait_timeout.tv_sec = 0; + signum_to_process[sig] = 1; + sig_to_process = 1; + return; + } + pthread_kernel_lock++; + + sig_count++; + sig_handler(sig); + + /* Handle any signals the current thread might have just gotten */ + if (pthread_run && pthread_run->sigcount) { + pthread_sig_process(); + } + pthread_kernel_lock--; +} + +/* ========================================================================== + * sig_handler_fake() + */ +void sig_handler_fake(int sig) +{ + if (pthread_kernel_lock) { + /* sigaddset((sigset_t *)&sig_to_process, sig); */ + signum_to_process[sig] = 1; + sig_to_process = 1; + return; + } + pthread_kernel_lock++; + sig_handler(sig); + while (!(--pthread_kernel_lock)) { + if (sig_to_process) { + /* if (SIG_ANY(sig_to_process)) { */ + pthread_kernel_lock++; + sig_handler(0); + } else { + break; + } + } +} + +/* ========================================================================== + * __pthread_signal_delete(int sig) + * + * Assumes the kernel is locked. + */ +void __pthread_signal_delete(int sig) +{ + signum_to_process[sig] = 0; +} + +/* ========================================================================== + * pthread_sched_other_resume() + * + * Check if thread to be resumed is of higher priority and if so + * stop current thread and start new thread. + */ +pthread_sched_other_resume(struct pthread * pthread) +{ + pthread->state = PS_RUNNING; + pthread_prio_queue_enq(pthread_current_prio_queue, pthread); + + if (pthread->pthread_priority > pthread_run->pthread_priority) { + if (pthread_kernel_lock == 1) { + sig_handler(SIGVTALRM); + } + } + + __cleanup_after_resume(); +} + +/* ========================================================================== + * pthread_resched_resume() + * + * This routine assumes that the caller is the current pthread, pthread_run + * and that it has a lock the kernel thread and it wants to reschedule itself. + */ +void pthread_resched_resume(enum pthread_state state) +{ + pthread_run->state = state; + + /* Since we are about to block this thread, lets see if we are + * at a cancel point and if we've been cancelled. + * Avoid cancelling dead or unalloced threads. + */ + if( ! TEST_PF_RUNNING_TO_CANCEL(pthread_run) && + TEST_PTHREAD_IS_CANCELLABLE(pthread_run) && + state != PS_DEAD && state != PS_UNALLOCED ) { + + /* Set this flag to avoid recursively calling pthread_exit */ + /* We have to set this flag here because we will unlock the + * kernel prior to calling pthread_cancel_internal. + */ + SET_PF_RUNNING_TO_CANCEL(pthread_run); + + pthread_run->old_state = state; /* unlock needs this data */ + pthread_sched_resume(); /* Unlock kernel before cancel */ + pthread_cancel_internal( 1 ); /* free locks and exit */ + } + + sig_handler(SIGVTALRM); + + __cleanup_after_resume(); +} + +/* ========================================================================== + * pthread_sched_resume() + */ +void pthread_sched_resume() +{ + __cleanup_after_resume(); +} + +/*---------------------------------------------------------------------- + * Function: __cleanup_after_resume + * Purpose: cleanup kernel locks after a resume + * Args: void + * Returns: void + * Notes: + *----------------------------------------------------------------------*/ +static void +__cleanup_after_resume( void ) +{ + /* Only bother if we are truely unlocking the kernel */ + while (!(--pthread_kernel_lock)) { + /* if (SIG_ANY(sig_to_process)) { */ + if (sig_to_process) { + pthread_kernel_lock++; + sig_handler(0); + continue; + } + if (pthread_run && pthread_run->sigcount) { + pthread_kernel_lock++; + pthread_sig_process(); + continue; + } + break; + } + + if( pthread_run == NULL ) + return; /* Must be during init processing */ + + /* Test for cancel that should be handled now */ + + if( ! TEST_PF_RUNNING_TO_CANCEL(pthread_run) && + TEST_PTHREAD_IS_CANCELLABLE(pthread_run) ) { + /* Kernel is already unlocked */ + pthread_cancel_internal( 1 ); /* free locks and exit */ + } +} + +/* ========================================================================== + * pthread_sched_prevent() + */ +void pthread_sched_prevent(void) +{ + pthread_kernel_lock++; +} + +/* ========================================================================== + * sig_init() + * + * SIGVTALRM (NOT POSIX) needed for thread timeslice timeouts. + * Since it's not POSIX I will replace it with a + * virtual timer for threads. + * SIGALRM (IS POSIX) so some special handling will be + * necessary to fake SIGALRM signals + */ +#ifndef SIGINFO +#define SIGINFO 0 +#endif +void sig_init(void) +{ + static const int signum_to_initialize[] = + { SIGCHLD, SIGALRM, SIGVTALRM, SIGINFO, 0 }; + static const int signum_to_ignore[] = { SIGKILL, SIGSTOP, 0 }; + int i, j; + +#if defined(HAVE_SYSCALL_SIGACTION) || defined(HAVE_SYSCALL_KSIGACTION) + struct sigaction act; + + act.sa_handler = sig_handler_real; + sigemptyset(&(act.sa_mask)); + act.sa_flags = 0; +#endif + + /* Initialize the important signals */ + for (i = 0; signum_to_initialize[i]; i++) { + +#if defined(HAVE_SYSCALL_SIGACTION) || defined(HAVE_SYSCALL_KSIGACTION) + if (sigaction(signum_to_initialize[i], &act, NULL)) { +#else + if (signal(signum_to_initialize[i], sig_handler_real)) { +#endif + PANIC(); + } + } + + /* Initialize the rest of the signals */ + for (j = 1; j < SIGMAX; j++) { + for (i = 0; signum_to_initialize[i]; i++) { + if (signum_to_initialize[i] == j) { + goto sig_next; + } + } + /* Because Solaris 2.4 can't deal -- proven */ + for (i = 0; signum_to_ignore[i]; i++) { + if (signum_to_ignore[i] == j) { + goto sig_next; + } + } + pthread_signal(j, SIG_DFL); + +#if defined(HAVE_SYSCALL_SIGACTION) || defined(HAVE_SYSCALL_KSIGACTION) + sigaction(j, &act, NULL); +#else + signal(j, sig_handler_real); +#endif + + sig_next:; + } + +#if defined BROKEN_SIGNALS + signal(SIGCHLD, sig_handler_real); +#endif + +} + |