summaryrefslogtreecommitdiff
path: root/mit-pthreads/pthreads/signal.c
diff options
context:
space:
mode:
Diffstat (limited to 'mit-pthreads/pthreads/signal.c')
-rw-r--r--mit-pthreads/pthreads/signal.c653
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
+
+}
+