/* * $Id: timer.c,v 1.9 2005/10/18 19:17:29 lutchann Exp $ * * Authors: * Pedro Roque * Lars Fenneberg * * 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 . * */ #include #include #include 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; }