mariadb/mit-pthreads/pthreads/signal.c
bk@work.mysql.com f4c589ff6c Import changeset
2000-07-31 21:29:14 +02:00

653 lines
18 KiB
C

/* ==== 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
}