205 lines
5.1 KiB
C
205 lines
5.1 KiB
C
/*
|
|
* $Id: timer.c,v 1.1.1.1 2010/09/30 21:01:53 josephxu Exp $
|
|
*
|
|
* Authors:
|
|
* Pedro Roque <roque@di.fc.ul.pt>
|
|
* Lars Fenneberg <lf@elemental.net>
|
|
*
|
|
* This software is Copyright 1996-2000 by the above mentioned author(s),
|
|
* All Rights Reserved.
|
|
*
|
|
* The license which is distributed with this software in the file COPYRIGHT
|
|
* applies to this software. If your distribution is missing this file, you
|
|
* may request it from <pekkas@netcore.fi>.
|
|
*
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <includes.h>
|
|
#include <radvd.h>
|
|
|
|
static struct timer_lst timers_head = {
|
|
{LONG_MAX, LONG_MAX},
|
|
NULL, NULL,
|
|
&timers_head, &timers_head
|
|
};
|
|
|
|
static void alarm_handler(int sig);
|
|
int inline check_time_diff(struct timer_lst *tm, struct timeval tv);
|
|
|
|
static void
|
|
schedule_timer(void)
|
|
{
|
|
struct timer_lst *tm = timers_head.next;
|
|
struct timeval tv;
|
|
|
|
gettimeofday(&tv, NULL);
|
|
|
|
if (tm != &timers_head)
|
|
{
|
|
struct itimerval next;
|
|
|
|
memset(&next, 0, sizeof(next));
|
|
|
|
timersub(&tm->expires, &tv, &next.it_value);
|
|
|
|
signal(SIGALRM, alarm_handler);
|
|
|
|
if ((next.it_value.tv_sec > 0) ||
|
|
((next.it_value.tv_sec == 0) && (next.it_value.tv_usec > 0)))
|
|
{
|
|
dlog(LOG_DEBUG, 4, "calling alarm: %ld secs, %ld usecs",
|
|
next.it_value.tv_sec, next.it_value.tv_usec);
|
|
|
|
if(setitimer(ITIMER_REAL, &next, NULL))
|
|
flog(LOG_WARNING, "schedule_timer setitimer for %ld.%ld failed: %s",
|
|
next.it_value.tv_sec, next.it_value.tv_usec, strerror(errno));
|
|
}
|
|
else
|
|
{
|
|
dlog(LOG_DEBUG, 4, "next timer has already expired, queueing signal");
|
|
kill(getpid(), SIGALRM);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
set_timer(struct timer_lst *tm, double secs)
|
|
{
|
|
struct timeval tv;
|
|
struct timer_lst *lst;
|
|
sigset_t bmask, oldmask;
|
|
struct timeval firein;
|
|
|
|
dlog(LOG_DEBUG, 3, "setting timer: %.2f secs", secs);
|
|
|
|
firein.tv_sec = (long)secs;
|
|
firein.tv_usec = (long)((secs - (double)firein.tv_sec) * 1000000);
|
|
|
|
dlog(LOG_DEBUG, 5, "setting timer: %ld secs %ld usecs", firein.tv_sec, firein.tv_usec);
|
|
|
|
gettimeofday(&tv, NULL);
|
|
timeradd(&tv, &firein, &tm->expires);
|
|
|
|
sigemptyset(&bmask);
|
|
sigaddset(&bmask, SIGALRM);
|
|
sigprocmask(SIG_BLOCK, &bmask, &oldmask);
|
|
|
|
lst = &timers_head;
|
|
|
|
/* the timers are in the list in the order they expire, the soonest first */
|
|
do {
|
|
lst = lst->next;
|
|
} while ((tm->expires.tv_sec > lst->expires.tv_sec) ||
|
|
((tm->expires.tv_sec == lst->expires.tv_sec) &&
|
|
(tm->expires.tv_usec > lst->expires.tv_usec)));
|
|
|
|
tm->next = lst;
|
|
tm->prev = lst->prev;
|
|
lst->prev = tm;
|
|
tm->prev->next = tm;
|
|
|
|
dlog(LOG_DEBUG, 5, "calling schedule_timer from set_timer context");
|
|
schedule_timer();
|
|
|
|
sigprocmask(SIG_SETMASK, &oldmask, NULL);
|
|
}
|
|
|
|
void
|
|
clear_timer(struct timer_lst *tm)
|
|
{
|
|
sigset_t bmask, oldmask;
|
|
|
|
sigemptyset(&bmask);
|
|
sigaddset(&bmask, SIGALRM);
|
|
sigprocmask(SIG_BLOCK, &bmask, &oldmask);
|
|
|
|
tm->prev->next = tm->next;
|
|
tm->next->prev = tm->prev;
|
|
|
|
tm->prev = tm->next = NULL;
|
|
|
|
dlog(LOG_DEBUG, 5, "calling schedule_timer from clear_timer context");
|
|
schedule_timer();
|
|
|
|
sigprocmask(SIG_SETMASK, &oldmask, NULL);
|
|
}
|
|
|
|
static void
|
|
alarm_handler(int sig)
|
|
{
|
|
struct timer_lst *tm, *back;
|
|
struct timeval tv;
|
|
gettimeofday(&tv, NULL);
|
|
tm = timers_head.next;
|
|
|
|
/*
|
|
* This handler is called when the alarm goes off, so at least one of
|
|
* the interfaces' timers should satisfy the while condition.
|
|
*
|
|
* Sadly, this is not always the case, at least on Linux kernels:
|
|
* see http://lkml.org/lkml/2005/4/29/163. :-(. It seems some
|
|
* versions of timers are not accurate and get called up to a couple of
|
|
* hundred microseconds before they expire.
|
|
*
|
|
* Therefore we allow some inaccuracy here; it's sufficient for us
|
|
* that a timer should go off in a millisecond.
|
|
*/
|
|
|
|
/* unused timers are initialized to LONG_MAX so we skip them */
|
|
while (tm->expires.tv_sec != LONG_MAX && check_time_diff(tm, tv))
|
|
{
|
|
tm->prev->next = tm->next;
|
|
tm->next->prev = tm->prev;
|
|
|
|
back = tm;
|
|
tm = tm->next;
|
|
back->prev = back->next = NULL;
|
|
|
|
(*back->handler)(back->data);
|
|
}
|
|
|
|
dlog(LOG_DEBUG, 5, "calling schedule_timer from alarm_handler context");
|
|
schedule_timer();
|
|
}
|
|
|
|
|
|
void
|
|
init_timer(struct timer_lst *tm, void (*handler)(void *), void *data)
|
|
{
|
|
memset(tm, 0, sizeof(struct timer_lst));
|
|
tm->handler = handler;
|
|
tm->data = data;
|
|
}
|
|
|
|
int inline
|
|
check_time_diff(struct timer_lst *tm, struct timeval tv)
|
|
{
|
|
struct itimerval diff;
|
|
memset(&diff, 0, sizeof(diff));
|
|
|
|
#define ALLOW_CLOCK_USEC 1000
|
|
|
|
timersub(&tm->expires, &tv, &diff.it_value);
|
|
dlog(LOG_DEBUG, 5, "check_time_diff, difference: %ld sec + %ld usec",
|
|
diff.it_value.tv_sec, diff.it_value.tv_usec);
|
|
|
|
if (diff.it_value.tv_sec <= 0) {
|
|
/* already gone, this is the "good" case */
|
|
if (diff.it_value.tv_sec < 0)
|
|
return 1;
|
|
#ifdef __linux__ /* we haven't seen this on other OSes */
|
|
/* still OK if the expiry time is not too much in the future */
|
|
else if (diff.it_value.tv_usec > 0 &&
|
|
diff.it_value.tv_usec <= ALLOW_CLOCK_USEC) {
|
|
dlog(LOG_DEBUG, 4, "alarm_handler clock was probably off by %ld usec, allowing %u",
|
|
tm->expires.tv_usec-tv.tv_usec, ALLOW_CLOCK_USEC);
|
|
return 2;
|
|
}
|
|
#endif /* __linux__ */
|
|
else /* scheduled intentionally in the future? */
|
|
return 0;
|
|
}
|
|
return 0;
|
|
}
|