446 lines
11 KiB
C
446 lines
11 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: signal.c,v 1.3 1994/02/07 22:04:29 proven Exp $ $provenid: signal.c,v 1.18 1994/02/07 02:19:28 proven Exp $";
|
|
#endif
|
|
|
|
#include <pthread.h>
|
|
#include <signal.h>
|
|
|
|
/*
|
|
* 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 volatile sigset_t sig_to_tryagain;
|
|
static volatile sigset_t sig_to_process;
|
|
static volatile int kernel_lock = 0;
|
|
static volatile int sig_count = 0;
|
|
|
|
static void sig_handler(int signal);
|
|
static void set_thread_timer();
|
|
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;
|
|
semaphore *lock;
|
|
int count;
|
|
|
|
/* save state of current thread */
|
|
if (machdep_save_state()) {
|
|
return;
|
|
}
|
|
|
|
last = pthread_run;
|
|
if (pthread_run = pthread_queue_deq(&pthread_current_queue)) {
|
|
/* restore state of new current thread */
|
|
machdep_restore_state();
|
|
return;
|
|
}
|
|
|
|
/* Poll all the kernel fds */
|
|
fd_kern_poll();
|
|
|
|
context_switch_reschedule:;
|
|
/*
|
|
* Go through the reschedule list once, this is the only place
|
|
* that goes through the queue without using the queue routines.
|
|
*
|
|
* But first delete the current queue.
|
|
*/
|
|
pthread_queue_init(&pthread_current_queue);
|
|
current = &(pthread_link_list);
|
|
count = 0;
|
|
|
|
while (*current) {
|
|
switch((*current)->state) {
|
|
case PS_RUNNING:
|
|
pthread_queue_enq(&pthread_current_queue, *current);
|
|
current = &((*current)->pll);
|
|
count++;
|
|
break;
|
|
case PS_DEAD:
|
|
/* Cleanup thread, unless we're using the stack */
|
|
if (((*current)->flags & PF_DETACHED) && (*current != last)) {
|
|
next = (*current)->pll;
|
|
lock = &((*current)->lock);
|
|
if (SEMAPHORE_TEST_AND_SET(lock)) {
|
|
/* Couldn't cleanup this time, try again later */
|
|
current = &((*current)->pll);
|
|
} else {
|
|
if (!((*current)->attr.stackaddr_attr)) {
|
|
free (machdep_pthread_cleanup(&((*current)->machdep_data)));
|
|
}
|
|
free (*current);
|
|
*current = next;
|
|
}
|
|
} else {
|
|
current = &((*current)->pll);
|
|
}
|
|
break;
|
|
default:
|
|
/* Should be on a different queue. Ignore. */
|
|
current = &((*current)->pll);
|
|
count++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Are there any threads to run */
|
|
if (pthread_run = pthread_queue_deq(&pthread_current_queue)) {
|
|
/* restore state of new current thread */
|
|
machdep_restore_state();
|
|
return;
|
|
}
|
|
|
|
/* Are there any threads at all */
|
|
if (count) {
|
|
/*
|
|
* Do a wait, timeout is set to a hour unless we get an interrupt
|
|
* before the select in wich case it polls and returns.
|
|
*/
|
|
fd_kern_wait();
|
|
|
|
/* Check for interrupts, but ignore SIGVTALR */
|
|
sigdelset(&sig_to_process, SIGVTALRM);
|
|
|
|
if (sig_to_process) {
|
|
/* Process interrupts */
|
|
sig_handler(0);
|
|
}
|
|
|
|
goto context_switch_reschedule;
|
|
|
|
}
|
|
exit(0);
|
|
}
|
|
|
|
/* ==========================================================================
|
|
* sig_handler_pause()
|
|
*
|
|
* Wait until a signal is sent to the process.
|
|
*/
|
|
void sig_handler_pause()
|
|
{
|
|
sigset_t sig_to_block, sig_to_pause;
|
|
|
|
sigfillset(&sig_to_block);
|
|
sigemptyset(&sig_to_pause);
|
|
sigprocmask(SIG_BLOCK, &sig_to_block, NULL);
|
|
if (!sig_to_process) {
|
|
sigsuspend(&sig_to_pause);
|
|
}
|
|
sigprocmask(SIG_UNBLOCK, &sig_to_block, NULL);
|
|
}
|
|
|
|
/* ==========================================================================
|
|
* 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(&sig_to_process, SIGVTALRM);
|
|
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.sched_attr) {
|
|
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();
|
|
}
|
|
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.sched_attr;
|
|
}
|
|
|
|
/* ==========================================================================
|
|
* sig_handler()
|
|
*
|
|
* Assumes the kernel is locked.
|
|
*/
|
|
static void sig_handler(int sig)
|
|
{
|
|
|
|
/*
|
|
* First check for old signals, do one pass through and don't
|
|
* check any twice.
|
|
*/
|
|
if (sig_to_tryagain) {
|
|
if (sigismember(&sig_to_tryagain, SIGALRM)) {
|
|
switch (sleep_wakeup()) {
|
|
case 1:
|
|
/* Do the default action, no threads were sleeping */
|
|
case OK:
|
|
/* Woke up a sleeping thread */
|
|
sigdelset(&sig_to_tryagain, SIGALRM);
|
|
break;
|
|
case NOTOK:
|
|
/* Couldn't get appropriate locks, try again later */
|
|
break;
|
|
}
|
|
} else {
|
|
PANIC();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* NOW, process signal that just came in, plus any pending on the
|
|
* signal mask. All of these must be resolved.
|
|
*/
|
|
|
|
sig_handler_top:;
|
|
|
|
switch(sig) {
|
|
case 0:
|
|
break;
|
|
case SIGVTALRM:
|
|
if (sig_count) {
|
|
sigset_t sigall;
|
|
|
|
sig_count = 0;
|
|
|
|
/* Unblock all signals */
|
|
sigemptyset(&sigall);
|
|
sigprocmask(SIG_SETMASK, &sigall, NULL);
|
|
}
|
|
context_switch();
|
|
context_switch_done();
|
|
break;
|
|
case SIGALRM:
|
|
sigdelset(&sig_to_process, SIGALRM);
|
|
switch (sleep_wakeup()) {
|
|
case 1:
|
|
/* Do the default action, no threads were sleeping */
|
|
case OK:
|
|
/* Woke up a sleeping thread */
|
|
break;
|
|
case NOTOK:
|
|
/* Couldn't get appropriate locks, try again later */
|
|
sigaddset(&sig_to_tryagain, SIGALRM);
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
PANIC();
|
|
}
|
|
|
|
/* Determine if there are any other signals */
|
|
if (sig_to_process) {
|
|
for (sig = 1; sig <= SIGMAX; sig++) {
|
|
if (sigismember(&sig_to_process, sig)) {
|
|
|
|
/* goto sig_handler_top */
|
|
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)
|
|
{
|
|
if (kernel_lock) {
|
|
__fd_kern_wait_timeout.tv_sec = 0;
|
|
sigaddset(&sig_to_process, sig);
|
|
return;
|
|
}
|
|
sig_prevent();
|
|
sig_count++;
|
|
sig_handler(sig);
|
|
sig_resume();
|
|
}
|
|
|
|
/* ==========================================================================
|
|
* sig_handler_fake()
|
|
*/
|
|
void sig_handler_fake(int sig)
|
|
{
|
|
if (kernel_lock) {
|
|
/* Currently this should be impossible */
|
|
PANIC();
|
|
}
|
|
sig_prevent();
|
|
sig_handler(sig);
|
|
sig_resume();
|
|
}
|
|
|
|
/* ==========================================================================
|
|
* reschedule()
|
|
*
|
|
* This routine assumes that the caller is the current pthread, pthread_run
|
|
* and that it has a lock on itself and that it wants to reschedule itself.
|
|
*/
|
|
void reschedule(enum pthread_state state)
|
|
{
|
|
semaphore *plock;
|
|
|
|
if (kernel_lock) {
|
|
/* Currently this should be impossible */
|
|
PANIC();
|
|
}
|
|
sig_prevent();
|
|
pthread_run->state = state;
|
|
SEMAPHORE_RESET((plock = &(pthread_run->lock)));
|
|
sig_handler(SIGVTALRM);
|
|
sig_resume();
|
|
}
|
|
|
|
/* ==========================================================================
|
|
* sig_prevent()
|
|
*/
|
|
void sig_prevent(void)
|
|
{
|
|
kernel_lock++;
|
|
}
|
|
|
|
/* ==========================================================================
|
|
* sig_resume()
|
|
*/
|
|
void sig_resume()
|
|
{
|
|
kernel_lock--;
|
|
}
|
|
|
|
/* ==========================================================================
|
|
* sig_check_and_resume()
|
|
*/
|
|
void sig_check_and_resume()
|
|
{
|
|
/* Some routine name that is yet to be determined. */
|
|
|
|
/* Only bother if we are truely unlocking the kernel */
|
|
while (!(--kernel_lock)) {
|
|
|
|
/* Assume sigset_t is not a struct or union */
|
|
if (sig_to_process) {
|
|
kernel_lock++;
|
|
sig_handler(0);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ==========================================================================
|
|
* 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
|
|
*/
|
|
void sig_init(void)
|
|
{
|
|
int sig_to_init[] = { SIGVTALRM, SIGALRM, 0 };
|
|
|
|
#if defined(SA_RESTART)
|
|
struct sigaction act;
|
|
#endif
|
|
|
|
int i;
|
|
|
|
#if defined(SA_RESTART)
|
|
act.sa_handler = sig_handler_real;
|
|
sigemptyset(&(act.sa_mask));
|
|
act.sa_flags = SA_RESTART;
|
|
#endif
|
|
|
|
/* Initialize only the necessary signals */
|
|
for (i = 0; sig_to_init[i]; i++) {
|
|
|
|
#if defined(SA_RESTART)
|
|
if (sigaction(sig_to_init[i], &act, NULL)) {
|
|
#else
|
|
if (signal(sig_to_init[i], sig_handler_real)) {
|
|
#endif
|
|
PANIC();
|
|
}
|
|
}
|
|
}
|
|
|