421 lines
11 KiB
C
Executable File
421 lines
11 KiB
C
Executable File
/* Copyright (c) 1993 SMI */
|
|
/* All Rights Reserved */
|
|
|
|
/* THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF SMI */
|
|
/* The copyright notice above does not evidence any */
|
|
/* actual or intended publication of such source code. */
|
|
|
|
#pragma ident "@(#)callout.c 1.32 95/08/29 SMI"
|
|
|
|
|
|
#include "libthread.h"
|
|
|
|
|
|
/*
|
|
* Global variables
|
|
*/
|
|
int _timerset = 0; /* interval timer is set if timerset == 1 */
|
|
thread_t _co_tid = 0; /* thread that does the callout processing */
|
|
int _co_set = 0; /* create only one thread to run callouts */
|
|
int _calloutcnt = 0; /* number of pending callouts */
|
|
callo_t *_calloutp = NULL; /* pointer to the callout queue */
|
|
mutex_t _calloutlock = DEFAULTMUTEX; /* protects queue of callouts */
|
|
|
|
/*
|
|
* Static variables
|
|
*/
|
|
static lwp_sema_t _settimer; /* signal co_timer() to set itimer */
|
|
static sigset_t _alrmmask; /* masks off SIGALRM */
|
|
|
|
/*
|
|
* Static functions
|
|
*/
|
|
static void _co_enable();
|
|
static void *_co_timerset(void *arg);
|
|
static void _delcallout(callo_t *cop);
|
|
static void _swapcallout(callo_t *cop1, callo_t *cop2);
|
|
static void _setrealitimer(struct timeval *clocktime,
|
|
struct timeval *tv);
|
|
|
|
/*
|
|
* The callouts are linked together by a double linked list.
|
|
* "calloutp points to the first element of this list. Elements
|
|
* are put onto the list in time of day order.
|
|
* If cop is already on the callout list (TIMER_ON), compute the
|
|
* time remaining (in secs) after which the callout is supposed
|
|
* to happen and delete the callout.
|
|
* If time in tv is 0, simply return this time value, otherwise,
|
|
* install the new callout and then return this time value.
|
|
*/
|
|
int
|
|
_setcallout(callo_t *cop, thread_t tid, const struct timeval *tv,
|
|
void (*func)(), int arg)
|
|
{
|
|
callo_t *cp, *pcp;
|
|
struct timeval clocktime;
|
|
sigset_t set;
|
|
struct itimerval it;
|
|
int ret = 0;
|
|
|
|
_lmutex_lock(&_calloutlock);
|
|
if (!_co_set) {
|
|
_co_set = 1;
|
|
sigemptyset(&_alrmmask);
|
|
sigaddset(&_alrmmask, SIGALRM);
|
|
_lmutex_unlock(&_calloutlock);
|
|
_co_enable();
|
|
_lmutex_lock(&_calloutlock);
|
|
}
|
|
_gettimeofday(&clocktime, 0);
|
|
if (cop->flag == CO_TIMER_ON) {
|
|
/*
|
|
* compute time remaining until this callout was
|
|
* supposed to happen and later return this time.
|
|
*/
|
|
if (cop->time.tv_sec > clocktime.tv_sec) {
|
|
ret = cop->time.tv_sec - clocktime.tv_sec;
|
|
}
|
|
_delcallout(cop);
|
|
}
|
|
cop->flag = CO_TIMER_OFF;
|
|
if (tv->tv_sec == 0 && tv->tv_usec == 0) {
|
|
cop->flag = CO_TIMEDOUT;
|
|
_lmutex_unlock(&_calloutlock);
|
|
return (ret);
|
|
}
|
|
clocktime.tv_sec += tv->tv_sec;
|
|
clocktime.tv_usec += tv->tv_usec;
|
|
if (clocktime.tv_usec >= 1000000) {
|
|
clocktime.tv_usec -= 1000000;
|
|
clocktime.tv_sec += 1;
|
|
}
|
|
_calloutcnt++;
|
|
ASSERT(_calloutcnt > 0);
|
|
cp = _calloutp;
|
|
pcp = NULL;
|
|
while (cp != NULL) {
|
|
if (clocktime.tv_sec < cp->time.tv_sec || (clocktime.tv_sec ==
|
|
cp->time.tv_sec && clocktime.tv_usec < cp->time.tv_usec))
|
|
break;
|
|
pcp = cp;
|
|
cp = cp->forw;
|
|
}
|
|
cop->flag = CO_TIMER_ON;
|
|
cop->time = clocktime;
|
|
cop->func = func;
|
|
cop->arg = arg;
|
|
cop->tid = tid;
|
|
if (cp == _calloutp) {
|
|
/* at the front of the list */
|
|
cop->forw = cp;
|
|
if (cp != NULL)
|
|
cp->backw = cop;
|
|
cop->backw = NULL;
|
|
_calloutp = cop;
|
|
_lmutex_unlock(&_calloutlock);
|
|
_lwp_sema_post(&_settimer);
|
|
return (ret);
|
|
} else if (cp == NULL) {
|
|
/* at the end of the list */
|
|
cop->forw = NULL;
|
|
pcp->forw = cop;
|
|
cop->backw = pcp;
|
|
} else {
|
|
/* in the middle of the list */
|
|
cp->backw->forw = cop;
|
|
cop->forw = cp;
|
|
cop->backw = cp->backw;
|
|
cp->backw = cop;
|
|
}
|
|
_lmutex_unlock(&_calloutlock);
|
|
return (ret);
|
|
}
|
|
|
|
/*
|
|
* remove a thread from the list of pending callouts.
|
|
*/
|
|
int
|
|
_rmcallout(callo_t *cop)
|
|
{
|
|
int timedout = 0;
|
|
|
|
_lmutex_lock(&_calloutlock);
|
|
if (cop->flag == CO_TIMER_ON)
|
|
_delcallout(cop);
|
|
else
|
|
timedout = (cop->flag == CO_TIMEDOUT);
|
|
cop->flag = CO_TIMER_OFF;
|
|
_lmutex_unlock(&_calloutlock);
|
|
return (timedout);
|
|
}
|
|
|
|
/*
|
|
* return 1 when callout has already timed out. otherwise
|
|
* return 0.
|
|
*/
|
|
static void
|
|
_delcallout(callo_t *cop)
|
|
{
|
|
_calloutcnt--;
|
|
if (cop == _calloutp) {
|
|
/* at front of list */
|
|
_calloutp = cop->forw;
|
|
if (_calloutp != NULL)
|
|
_calloutp->backw = NULL;
|
|
} else if (cop->forw == NULL)
|
|
/* at end of list */
|
|
cop->backw->forw = NULL;
|
|
else {
|
|
/* in middle of the list */
|
|
cop->backw->forw = cop->forw;
|
|
cop->forw->backw = cop->backw;
|
|
}
|
|
}
|
|
|
|
static void
|
|
_swapcallout(callo_t *cop1, callo_t *cop2)
|
|
{
|
|
*cop2 = *cop1;
|
|
if (cop1 == _calloutp) {
|
|
_calloutp = cop2;
|
|
if (cop1->forw)
|
|
cop1->forw->backw = cop2;
|
|
} else if (cop1->forw == NULL) {
|
|
cop1->backw->forw = cop2;
|
|
} else {
|
|
cop1->forw->backw = cop2;
|
|
cop1->backw->forw = cop2;
|
|
}
|
|
cop1->forw = cop1->backw = NULL;
|
|
}
|
|
|
|
/*
|
|
* Note that _callin should be executed with all signals blocked. This is
|
|
* because the callout thread executing this handler is a daemon thread
|
|
* and should have all signals blocked at all times so as to not violate the
|
|
* virtual process mask.
|
|
*/
|
|
void
|
|
_callin(int sig, siginfo_t *sip, ucontext_t *uap, sigset_t *tmask)
|
|
{
|
|
struct timeval clocktime;
|
|
struct itimerval it;
|
|
callo_t *cop;
|
|
callo_t placeholder;
|
|
thread_t tid = _thr_self();
|
|
int err;
|
|
struct sigaction act;
|
|
|
|
if (tid != _co_tid) {
|
|
|
|
/*
|
|
* Here, calling the _lwp_mutex_*() primitives is ok and not
|
|
* the async safe version, since all signals are blocked on
|
|
* the thread at this point.
|
|
*/
|
|
_lwp_mutex_lock(&_sighandlerlock);
|
|
act = __alarm_sigaction;
|
|
_lwp_mutex_unlock(&_sighandlerlock);
|
|
|
|
if (act.sa_handler != SIG_DFL && act.sa_handler != SIG_IGN) {
|
|
if (act.sa_handler == _callin)
|
|
_panic("_callin: recursive");
|
|
/*
|
|
* Emulate the SA_RESETHAND flag, if set, here.
|
|
*/
|
|
if (act.sa_flags & SA_RESETHAND)
|
|
__sig_resethand(sig);
|
|
/*
|
|
* The thread mask has been changed by sigacthandler().
|
|
* The old mask is passed in "tmask". So, before calling
|
|
* the user installed segv handler, establish the
|
|
* correct mask.
|
|
*/
|
|
sigorset(&act.sa_mask, tmask);
|
|
thr_sigsetmask(SIG_SETMASK, &act.sa_mask, NULL);
|
|
(*act.sa_handler)(sig, sip, uap);
|
|
/*
|
|
* old thread mask will be restored in sigacthandler
|
|
* which called _callin().
|
|
*/
|
|
return;
|
|
} else if (act.sa_handler == SIG_DFL) {
|
|
/* send SIGALRM to myself and blow myself away */
|
|
__sigaction(SIGALRM, &act, NULL);
|
|
if (_lwp_kill(curthread->t_lwpid, SIGALRM) < 0) {
|
|
_panic("_callin: _lwp_kill");
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
_timerset--;
|
|
_gettimeofday(&clocktime, 0);
|
|
_lmutex_lock(&_calloutlock);
|
|
cop = _calloutp;
|
|
while (cop != NULL) {
|
|
if (clocktime.tv_sec > cop->time.tv_sec ||
|
|
(clocktime.tv_sec == cop->time.tv_sec &&
|
|
clocktime.tv_usec >= cop->time.tv_usec)) {
|
|
cop->flag = CO_TIMEDOUT;
|
|
_swapcallout(cop, &placeholder);
|
|
cop->running = 1;
|
|
_lmutex_unlock(&_calloutlock);
|
|
(*cop->func)(cop->arg);
|
|
_lmutex_lock(&_calloutlock);
|
|
cop->running = 0;
|
|
_cond_signal(&cop->waiting);
|
|
_delcallout(&placeholder);
|
|
cop = placeholder.forw;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
ASSERT((cop == NULL)?(_calloutp == NULL):1);
|
|
_lmutex_unlock(&_calloutlock);
|
|
/*
|
|
* re-enable realtime interval timer for next callout.
|
|
*/
|
|
if (cop) {
|
|
_setrealitimer(&clocktime, &cop->time);
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* A bound thread is in charge of dispatching callouts by setting a real
|
|
* time interval timer. The interval is set to the earliest arrival time of
|
|
* a callout in the list of callouts.
|
|
*
|
|
* This routine assumes that it is not re-entrant with respect to SIGALRM.
|
|
*/
|
|
static void
|
|
_setrealitimer(struct timeval *clocktime, struct timeval *tv)
|
|
{
|
|
struct itimerval it;
|
|
int err;
|
|
sigset_t otmask;
|
|
ASSERT(ISBOUND(curthread));
|
|
|
|
#ifdef BUG_1156578
|
|
if (clocktime->tv_usec > 1000000) {
|
|
clocktime->tv_usec -= 1000000;
|
|
clocktime->tv_sec++;
|
|
}
|
|
#endif /* 1156578 */
|
|
|
|
it.it_interval.tv_sec = it.it_interval.tv_usec = 0;
|
|
it.it_value.tv_usec = tv->tv_usec - clocktime->tv_usec;
|
|
it.it_value.tv_sec = tv->tv_sec - clocktime->tv_sec;
|
|
_timerset++;
|
|
if (it.it_value.tv_sec < 0 || (it.it_value.tv_sec == 0 &&
|
|
it.it_value.tv_usec <= 0)) {
|
|
/* interval has expired before it could be set */
|
|
ASSERT(curthread->t_tid == _co_tid);
|
|
if (_calloutp != NULL) {
|
|
/*
|
|
* Since this is called only from the bound daemon
|
|
* thread, use the LWP signal mask.
|
|
*/
|
|
__sigprocmask(SIG_BLOCK, &_alrmmask, &otmask);
|
|
_callin(NULL, NULL, NULL, NULL);
|
|
__sigprocmask(SIG_SETMASK, &otmask, NULL);
|
|
}
|
|
} else {
|
|
/*
|
|
* calculate the elapsed time at which the real time interval
|
|
* timer should expire. It is calculated by subtracting
|
|
* the current clock time from the arrival time. The times
|
|
* must be converted to micro seconds.
|
|
*/
|
|
if (it.it_value.tv_sec > 0 && it.it_value.tv_usec < 0) {
|
|
it.it_value.tv_sec--;
|
|
/* 1000000 usec == 1 sec */
|
|
it.it_value.tv_usec += 1000000;
|
|
}
|
|
ASSERT(it.it_value.tv_sec > 0 || it.it_value.tv_usec > 0);
|
|
err = __setitimer(ITIMER_REAL, &it, NULL);
|
|
if (err) printf("err = %d\n", err);
|
|
ASSERT(err == 0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* co_timerset() represents a bound thread that is invisible to
|
|
* the user. this thread is in charge of setting the the realtime
|
|
* interval timer and running _callin() on those callouts that have
|
|
* transpired.
|
|
*/
|
|
static struct sigaction __co_sigaction = {SA_RESTART, _callin, 0};
|
|
|
|
static void *
|
|
_co_timerset(void *arg)
|
|
{
|
|
struct timeval clocktime;
|
|
callo_t cop;
|
|
register int calltimer;
|
|
|
|
curthread->t_flag |= T_INTERNAL;
|
|
__sighandler_lock();
|
|
if (__alarm_sigaction.sa_handler == SIG_DFL ||
|
|
__alarm_sigaction.sa_handler == SIG_IGN) {
|
|
sigfillset(&__co_sigaction.sa_mask);
|
|
if (_setsighandler(SIGALRM, &__co_sigaction, NULL) == -1) {
|
|
__sighandler_unlock();
|
|
_panic("co_timerset, unable to install signal hndlr");
|
|
}
|
|
}
|
|
__sighandler_unlock();
|
|
maskallsigs(&curthread->t_hold);
|
|
sigdelset(&curthread->t_hold, SIGALRM);
|
|
/*
|
|
* should call __sigprocmask() here - calling thr_sigsetmask() here
|
|
* could mean losing an undirected signal forever since thr_sigsetmask
|
|
* is now a fast call which will not set the lwp's mask and relies
|
|
* on subsequent unblocking to get any pending signals.
|
|
*/
|
|
__sigprocmask(SIG_BLOCK, &curthread->t_hold, NULL);
|
|
while (1) {
|
|
/*
|
|
* Push down the thread mask here to the LWP.
|
|
*/
|
|
__sigprocmask(SIG_UNBLOCK, &_alrmmask, NULL);
|
|
_lwp_sema_wait(&_settimer);
|
|
__sigprocmask(SIG_BLOCK, &_alrmmask, NULL);
|
|
/*
|
|
* Block SIGALRM here on the LWP. Do not rely on
|
|
* thr_sigsetmask() - this is a thread which relies on
|
|
* synchronous delivery of SIGALRM to itself - so need
|
|
* to push down the mask everytime. Use __sigprocmask().
|
|
*/
|
|
calltimer = 0;
|
|
_lmutex_lock(&_calloutlock);
|
|
if (_calloutp != NULL) {
|
|
cop = *_calloutp;
|
|
calltimer = 1;
|
|
}
|
|
_lmutex_unlock(&_calloutlock);
|
|
if (calltimer) {
|
|
_gettimeofday(&clocktime, NULL);
|
|
_setrealitimer(&clocktime, &(cop.time));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
_co_enable()
|
|
{
|
|
int err;
|
|
|
|
err = _thr_create(NULL, 0, _co_timerset, NULL,
|
|
THR_SUSPENDED|THR_BOUND|THR_DAEMON, &_co_tid);
|
|
_thr_continue(_co_tid);
|
|
if (err) {
|
|
printf("co_enable, thr_create() returned error = %d\n",
|
|
errno);
|
|
_panic("co_enable failed");
|
|
}
|
|
_sched_lock();
|
|
--_totalthreads;
|
|
_sched_unlock();
|
|
}
|