# /* timer.c */ /* EMACS_MODES: c !fill */ /* This file contains the routines which make up the timer task and * its associated subroutines for setting and clearing timers. The * timer task manages a queue of timers, each of which specifies a * subroutine to be called (in the context of a timer task) when the * timer goes off. * The following routines are included in this package: * tm_set set a timer to go off at a specified time * tm_reset reset a timer to go off at a different time * tm_clear clear a previously set timer * tm_main the main routine of the timer task * tm_init init the timer system * tm_off turn off timer interrupts * tm_on turn timer interrupts back on */ #include #include #include #include "task.h" #include "timer.h" #define TIMERHIWATER 30 /* number of free timers to keep */ #define TIMERSTACK 100 /* only need a small stack */ static task *tm_task; /* timer task's handle */ static time_q tm_queue; /* queue of active timers */ static time_q tm_free; /* queue of free timers */ static nonce tnonce; /* timer nonce generator */ extern int tm_signal (); /* alarm signal catching routine */ tm_set (nsecs, subr, arg, ptid) /* Set a timer to go off after nsecs seconds. When the timer goes * off, call the specified subroutine with the specified argument. * flag. This routine runs in the context of the caller's task; * it just enqueues the timer and, if it is the first timer on the * queue, sets an alarm to awaken the timer task in nsecs seconds. * It returns a "timer identifier" in the structure pointed to by * ptid if ptid is non-null. This timer identifier may be presented * to tm_clear to clear the timer before it goes off. Also, returns * FALSE if unable to allocate a timer, and TRUE otherwise. * For efficiency, up to TIMERHIWATER free timers are kept on a * free queue rather than being deallocated. * * Arguments: */ register int nsecs; /* timer expiration time in seconds */ int (*subr) (); /* subroutine to call on expiration */ caddr_t arg; /* arg to pass to subr. */ timerid *ptid; /* place to return timer id */ { register timer *tm; /* timer being enqueued */ register timer **tmp; /* temp for chaining */ time_t now; /* current time */ if ((tm = (timer *)q_deq (&tm_free)) == NULL && (tm = (timer *)calloc (1, sizeof (timer))) == NULL) return (FALSE); time (&now); tm->tm_elt.qe_next = NULL; /* no next element */ tm->tm_time = now + nsecs; /* timer expiration time */ tm->tm_nonce = ++tnonce; /* nonce for this timer */ tm->tm_subr = subr; /* subroutine to call */ tm->tm_arg = arg; /* argument to pass */ for (tmp = &tm_queue.tmq_head; /* add to queue in timeout order */ *tmp != NULL && tm->tm_time >= (*tmp)->tm_time; tmp = (timer **)(((timer *)tmp)->tm_elt.qe_next)) ; q_adda (&tm_queue, tmp, tm); if (tm_queue.tmq_head == tm) /* first elt in queue? */ alarm (nsecs); /* yes, wake up timer task then */ if (ptid != NULL) { /* need to return timer id? */ ptid->ti_ptr = tm; /* yes */ ptid->ti_nonce = tm->tm_nonce; } return (TRUE); } tm_reset (nsecs, ptid) /* Reset the (running) timer specified by ptid to go off in nsecs * instead of at the time it is currently set for. If in fact the * timer is not already set, return FALSE; otherwise return TRUE. * ptid points to a timer identifier as returned by tm_set. This * routine does not modify the routine or argument in the timer. * * Arguments: */ register int nsecs; /* timer expiration time in seconds */ timerid *ptid; /* timer id to reset*/ { register timer *tm; /* timer being enqueued */ register timer **tmp; /* temp for chaining */ time_t now; /* current time */ tm = ptid->ti_ptr; /* get the timer pointer */ if (tm->tm_nonce != ptid->ti_nonce || !q_del (&tm_queue, tm)) return (FALSE); /* timer expired, give up */ time (&now); tm->tm_elt.qe_next = NULL; /* no next element */ tm->tm_time = now + nsecs; /* timer expiration time */ for (tmp = &tm_queue.tmq_head; /* add to queue in timeout order */ *tmp != NULL && tm->tm_time >= (*tmp)->tm_time; tmp = (timer **)(((timer *)tmp)->tm_elt.qe_next)) ; q_adda (&tm_queue, tmp, tm); if (tm_queue.tmq_head == tm) /* first elt in queue? */ alarm (nsecs); /* yes, wake up timer task then */ return (TRUE); } tm_clear (ptid) /* Clear the timer specified by the passed timer identifier. The timer * identifier gives a pointer to the timer to be cleared; in addition, * it includes the nonce assigned to the timer when it was set. The * nonce must match the timer's current nonce; this avoids the problem * of attempting to clear an expired timer. Free the timer's storage * (into the free list up to TIMERHIWATER elements). * If it was the only timer on the queue, reset the pending alarm. * Returns FALSE if the specified alarm was not found in the queue * or the nonce did not match, TRUE otherwise. * * Arguments: */ register timerid *ptid; /* identifier of timer to clear */ { register timer *tm; /* ptr to the timer itself */ tm = ptid->ti_ptr; if (tm->tm_nonce != ptid->ti_nonce || !q_del (&tm_queue, tm)) return (FALSE); /* timer expired, give up */ if (tm_queue.tmq_head == NULL) /* last elt on queue? */ alarm (0); /* yes, turn off alarm */ tm->tm_nonce = 0; /* make sure nonces won't match */ if (tm_free.tmq_len < TIMERHIWATER) { /* free the storage */ q_addh (&tm_free, tm); /* into free queue */ } else { cfree (tm); /* or back to pool */ } return (TRUE); /* success */ } tm_main () /* This routine forms the body of the timer management task. Its * job is simply to dequeue expired timers from the timer queue * and call the subroutine specified therein, passing it the argument * specified therein. This task is normally blocked on * the alarm signal. It is started by the tm_init() routine. * Note that this task needs the alarm signal, so all timer * management in the process must be via this task - no one else * may use the alarm() or sleep() calls! */ { register timer *tm; /* temp for holding timer */ time_t now; /* current time */ for (;;) { /* never exit */ tk_block (); /* sleep 'til a timer expires */ time (&now); /* current time */ while ((tm = tm_queue.tmq_head) != NULL && now >= tm->tm_time) { /* for all expired timers */ tm = (timer *)q_deq (&tm_queue); /* dequeue it */ (*tm->tm_subr) (tm->tm_arg); /* call the routine */ tm->tm_nonce = 0; /* show timer expired... */ if (tm_free.tmq_len < TIMERHIWATER) { /* free store */ q_addh (&tm_free, tm); } else { cfree (tm); } } if ((tm = tm_queue.tmq_head) != NULL) /* outstanding timers? */ alarm ((int)(tm->tm_time - now)); /* yes, set alarm */ } } tm_init () /* Initialize the timer package. Fork off the timer task. Also set * up the alarm signal-processing routine tm_signal() and the global * tm_task which it uses. */ { register task *tk; /* timer task's handle */ tm_task = tk = tk_fork (tm_main, TIMERSTACK, 0); /* start him up */ signal (SIGALRM, tm_signal); /* catch timer signal */ } tm_signal (signo) /* Signal handler for the SIGALRM signal. Just wake up the timer task, * reenable catching the alarm signal, and leave. * * Arguments: */ int signo; /* signal number */ { tk_wake (tm_task); /* wake up timer task */ signal (SIGALRM, tm_signal); } tm_off () /* Turn off timer interrupts. This is useful while writing data to * the terminal. */ { alarm (0); } tm_on () /* Turn timer interrupts back on. This is done by waking up the timer * task to process any events that went off while timer interrupts were * disabled and to start a new alarm. */ { tk_wake (tm_task); }