/* * Cisco router simulation platform. * Copyright (c) 2005,2006 Christophe Fillot (cf@utc.fr) * * timer.c: Management of timers. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" #include "mempool.h" #include "hash.h" #include "timer.h" /* Lock and unlock access to global structures */ #define TIMER_LOCK() pthread_mutex_lock(&timer_mutex) #define TIMER_UNLOCK() pthread_mutex_unlock(&timer_mutex) /* Pool of Timer Queues */ static timer_queue_t *timer_queue_pool = NULL; /* Hash table to map Timer ID to timer entries */ static hash_table_t *timer_id_hash = NULL; /* Last ID used. */ static timer_id timer_next_id = 1; /* Mutex to access to global structures (Hash Tables, Pool of queues, ...) */ static pthread_mutex_t timer_mutex = PTHREAD_MUTEX_INITIALIZER; /* Find a timer by its ID */ static inline timer_entry_t *timer_find_by_id(timer_id id) { return(hash_table_lookup(timer_id_hash,&id)); } /* Allocate a new ID. Disgusting method but it should work. */ static inline timer_id timer_alloc_id(void) { while(hash_table_lookup(timer_id_hash,&timer_next_id)) timer_next_id++; return(timer_next_id); } /* Free an ID */ static inline void timer_free_id(timer_id id) { hash_table_remove(timer_id_hash,&id); } /* * Select the queue of the pool that has the lowest criticity level. This * is a stupid method. */ timer_queue_t *timer_select_queue_from_pool(void) { timer_queue_t *s_queue,*queue; int level; /* to begin, select the first queue of the pool */ s_queue = timer_queue_pool; level = s_queue->level; /* walk through timer queues */ for(queue=timer_queue_pool->next;queue;queue=queue->next) { if (queue->level < level) { level = queue->level; s_queue = queue; } } /* returns selected queue */ return s_queue; } /* Add a timer in a queue */ static inline void timer_add_to_queue(timer_queue_t *queue, timer_entry_t *timer) { timer_entry_t *t,*prev = NULL; /* Insert after the last timer with the same or earlier time */ for(t=queue->list;t;t=t->next) { if (t->expire > timer->expire) break; prev = t; } /* Add it in linked list */ timer->next = t; timer->prev = prev; timer->queue = queue; if (timer->next) timer->next->prev = timer; if (timer->prev) timer->prev->next = timer; else queue->list = timer; /* Increment number of timers in queue */ queue->timer_count++; /* Increment criticity level */ queue->level += timer->level; } /* Add a timer in a queue atomically */ static inline void timer_add_to_queue_atomic(timer_queue_t *queue, timer_entry_t *timer) { TIMERQ_LOCK(queue); timer_add_to_queue(queue,timer); TIMERQ_UNLOCK(queue); } /* Remove a timer from queue */ static inline void timer_remove_from_queue(timer_queue_t *queue, timer_entry_t *timer) { if (timer->prev) timer->prev->next = timer->next; else queue->list = timer->next; if (timer->next) timer->next->prev = timer->prev; timer->next = timer->prev = NULL; /* Decrement number of timers in queue */ queue->timer_count--; /* Decrement criticity level */ queue->level -= timer->level; } /* Remove a timer from a queue atomically */ static inline void timer_remove_from_queue_atomic(timer_queue_t *queue,timer_entry_t *timer) { TIMERQ_LOCK(queue); timer_remove_from_queue(queue,timer); TIMERQ_UNLOCK(queue); } /* Free ressources used by a timer */ static inline void timer_free(timer_entry_t *timer,int take_lock) { if (take_lock) TIMER_LOCK(); /* Remove ID from hash table */ hash_table_remove(timer_id_hash,&timer->id); if (take_lock) TIMER_UNLOCK(); /* Free memory used by timer */ free(timer); } /* Run timer action */ static inline int timer_exec(timer_entry_t *timer) { return(timer->callback(timer->user_arg,timer)); } /* Schedule a timer in a queue */ static inline void timer_schedule_in_queue(timer_queue_t *queue, timer_entry_t *timer) { m_tmcnt_t current,current_adj; /* Set new expiration date and clear "run" flag */ if (timer->flags & TIMER_BOUNDARY) { current_adj = m_gettime_adj(); current = m_gettime(); timer->expire = current + timer->offset + (timer->interval - (current_adj % timer->interval)); } else timer->expire += timer->interval; timer->flags &= ~TIMER_RUNNING; timer_add_to_queue(queue,timer); } /* Schedule a timer */ static int timer_schedule(timer_entry_t *timer) { timer_queue_t *queue; /* Select the least used queue of the pool */ if (!(queue = timer_select_queue_from_pool())) { fprintf(stderr, "timer_schedule: no pool available for timer with ID %llu", timer->id); return(-1); } /* Reschedule it in queue */ TIMERQ_LOCK(queue); timer_schedule_in_queue(queue,timer); TIMERQ_UNLOCK(queue); return(0); } /* Timer loop */ static void *timer_loop(timer_queue_t *queue) { struct timespec t_spc; timer_entry_t *timer; m_tmcnt_t c_time; /* We allow thread cancellation at any time */ pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL); pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL); /* Set signal properties */ m_signal_block(SIGINT); m_signal_block(SIGQUIT); m_signal_block(SIGTERM); for(;;) { /* Prevent asynchronous access problems */ TIMERQ_LOCK(queue); /* Get first event */ timer = queue->list; /* * If we have timers in queue, we setup a timer to wait for first one. * In all cases, thread is woken up when a reschedule occurs. */ if (timer) { t_spc.tv_sec = timer->expire / 1000; t_spc.tv_nsec = (timer->expire % 1000) * 1000000; pthread_cond_timedwait(&queue->schedule,&queue->lock,&t_spc); } else { /* We just wait for reschedule since we don't have any timer */ pthread_cond_wait(&queue->schedule,&queue->lock); } /* We need to check "running" flags to know if we must stop */ if (!queue->running) { TIMERQ_UNLOCK(queue); break; } /* * Now, we need to find why we were woken up. So, we compare current * time with first timer to see if we must execute action associated * with it. */ c_time = m_gettime(); /* Get first event */ timer = queue->list; /* If there is nothing to do for now, wait again */ if ((timer == NULL) || (timer->expire > c_time)) { TIMERQ_UNLOCK(queue); continue; } /* * We have a timer to manage. Remove it from queue and mark it as * running. */ timer_remove_from_queue(queue,timer); timer->flags |= TIMER_RUNNING; /* Execute user function and reschedule timer if required */ if (timer_exec(timer)) timer_schedule_in_queue(queue,timer); TIMERQ_UNLOCK(queue); } /* Stop thread immediately */ pthread_exit(NULL); return NULL; } /* Remove a timer */ int timer_remove(timer_id id) { timer_queue_t *queue = NULL; timer_entry_t *timer; TIMER_LOCK(); /* Find timer */ if (!(timer = timer_find_by_id(id))) { TIMER_UNLOCK(); return(-1); } /* If we have a queue, remove timer from it atomically */ if (timer->queue) { queue = timer->queue; timer_remove_from_queue_atomic(queue,timer); } /* Release timer ID */ timer_free_id(id); /* Free memory used by timer */ free(timer); TIMER_UNLOCK(); /* Signal to this queue that it has been modified */ if (queue) pthread_cond_signal(&queue->schedule); return(0); } /* Enable a timer */ static timer_id timer_enable(timer_entry_t *timer) { /* Allocate a new ID */ TIMER_LOCK(); timer->id = timer_alloc_id(); /* Insert ID in hash table */ if (hash_table_insert(timer_id_hash,&timer->id,timer) == -1) { TIMER_UNLOCK(); free(timer); return(0); } /* Schedule event */ if (timer_schedule(timer) == -1) { timer_free(timer,FALSE); timer = NULL; TIMER_UNLOCK(); return(0); } /* Returns timer ID */ TIMER_UNLOCK(); pthread_cond_signal(&timer->queue->schedule); return(timer->id); } /* Create a new timer */ timer_id timer_create_entry(m_tmcnt_t interval,int boundary,int level, timer_proc callback,void *user_arg) { timer_entry_t *timer; /* Allocate memory for new timer entry */ if (!(timer = malloc(sizeof(*timer)))) return(0); timer->interval = interval; timer->offset = 0; timer->callback = callback; timer->user_arg = user_arg; timer->flags = 0; timer->level = level; /* Set expiration delay */ if (boundary) { timer->flags |= TIMER_BOUNDARY; } else timer->expire = m_gettime(); return(timer_enable(timer)); } /* Create a timer on boundary, with an offset */ timer_id timer_create_with_offset(m_tmcnt_t interval,m_tmcnt_t offset, int level,timer_proc callback,void *user_arg) { timer_entry_t *timer; /* Allocate memory for new timer entry */ if (!(timer = malloc(sizeof(*timer)))) return(0); timer->interval = interval; timer->offset = 0; timer->callback = callback; timer->user_arg = user_arg; timer->flags = 0; timer->level = level; timer->flags |= TIMER_BOUNDARY; return(timer_enable(timer)); } /* Set a new interval for a timer */ int timer_set_interval(timer_id id,long interval) { timer_queue_t *queue; timer_entry_t *timer; TIMER_LOCK(); /* Locate timer */ if (!(timer = timer_find_by_id(id))) { TIMER_UNLOCK(); return(-1); } queue = timer->queue; TIMERQ_LOCK(queue); /* Compute new expiration date */ timer->interval = interval; timer->expire = m_gettime() + (m_tmcnt_t)interval; timer_remove_from_queue(queue,timer); timer_schedule_in_queue(queue,timer); TIMERQ_UNLOCK(queue); TIMER_UNLOCK(); /* Reschedule */ pthread_cond_signal(&queue->schedule); return(0); } /* Create a new timer queue */ timer_queue_t *timer_create_queue(void) { timer_queue_t *queue; /* Create new queue structure */ if (!(queue = malloc(sizeof(*queue)))) return NULL; queue->running = TRUE; queue->list = NULL; queue->level = 0; /* Create mutex */ if (pthread_mutex_init(&queue->lock,NULL)) goto error; /* Create condition */ if (pthread_cond_init(&queue->schedule,NULL)) goto error; /* Create thread */ if (pthread_create(&queue->thread,NULL,(void *(*)(void *))timer_loop,queue)) goto error; return queue; error: free(queue); return NULL; } /* Flush queues */ void timer_flush_queues(void) { timer_entry_t *timer,*next_timer; timer_queue_t *queue,*next_queue; pthread_t thread; TIMER_LOCK(); for(queue=timer_queue_pool;queue;queue=next_queue) { TIMERQ_LOCK(queue); next_queue = queue->next; thread = queue->thread; /* mark queue as not running */ queue->running = FALSE; /* suppress all timers */ for(timer=queue->list;timer;timer=next_timer) { next_timer = timer->next; timer_free_id(timer->id); free(timer); } TIMERQ_UNLOCK(queue); /* signal changes to the queue thread */ pthread_cond_signal(&queue->schedule); /* wait for thread to terminate */ pthread_join(thread,NULL); pthread_cond_destroy(&queue->schedule); pthread_mutex_destroy(&queue->lock); free(queue); } TIMER_UNLOCK(); } /* Add a specified number of queues to the pool */ int timer_pool_add_queues(int nr_queues) { timer_queue_t *queue; int i; for(i=0;inext = timer_queue_pool; timer_queue_pool = queue; TIMER_UNLOCK(); } return(0); } /* Initialize timer sub-system */ int timer_init(void) { /* Initialize hash table which maps ID to timer entries */ if (!(timer_id_hash = hash_u64_create(TIMER_HASH_SIZE))) { fprintf(stderr,"timer_init: unable to create hash table."); return(-1); } /* Initialize default queues. If this fails, try to continue. */ if (timer_pool_add_queues(TIMERQ_NUMBER) == -1) { fprintf(stderr, "timer_init: unable to initialize at least one timer queue."); } return(0); }