1 |
/* |
2 |
* Cisco 7200 (Predator) simulation platform. |
3 |
* Copyright (c) 2005,2006 Christophe Fillot (cf@utc.fr) |
4 |
* |
5 |
* timer.c: Management of timers. |
6 |
*/ |
7 |
|
8 |
#include <stdio.h> |
9 |
#include <stdlib.h> |
10 |
#include <string.h> |
11 |
#include <stdarg.h> |
12 |
#include <unistd.h> |
13 |
#include <errno.h> |
14 |
#include <time.h> |
15 |
#include <fcntl.h> |
16 |
#include <signal.h> |
17 |
#include <sys/stat.h> |
18 |
#include <sys/types.h> |
19 |
#include <sys/socket.h> |
20 |
#include <sys/time.h> |
21 |
#include <netinet/in.h> |
22 |
#include <arpa/inet.h> |
23 |
#include <netdb.h> |
24 |
#include <pthread.h> |
25 |
|
26 |
#include "utils.h" |
27 |
#include "mempool.h" |
28 |
#include "hash.h" |
29 |
#include "timer.h" |
30 |
|
31 |
/* Lock and unlock access to global structures */ |
32 |
#define TIMER_LOCK() pthread_mutex_lock(&timer_mutex) |
33 |
#define TIMER_UNLOCK() pthread_mutex_unlock(&timer_mutex) |
34 |
|
35 |
/* Pool of Timer Queues */ |
36 |
static timer_queue_t *timer_queue_pool = NULL; |
37 |
|
38 |
/* Hash table to map Timer ID to timer entries */ |
39 |
static hash_table_t *timer_id_hash = NULL; |
40 |
|
41 |
/* Last ID used. */ |
42 |
static timer_id timer_next_id = 1; |
43 |
|
44 |
/* Mutex to access to global structures (Hash Tables, Pool of queues, ...) */ |
45 |
static pthread_mutex_t timer_mutex = PTHREAD_MUTEX_INITIALIZER; |
46 |
|
47 |
|
48 |
/* Find a timer by its ID */ |
49 |
static inline timer_entry_t *timer_find_by_id(timer_id id) |
50 |
{ |
51 |
return(hash_table_lookup(timer_id_hash,&id)); |
52 |
} |
53 |
|
54 |
/* Allocate a new ID. Disgusting method but it should work. */ |
55 |
static inline timer_id timer_alloc_id(void) |
56 |
{ |
57 |
while(hash_table_lookup(timer_id_hash,&timer_next_id)) |
58 |
timer_next_id++; |
59 |
|
60 |
return(timer_next_id); |
61 |
} |
62 |
|
63 |
/* Free an ID */ |
64 |
static inline void timer_free_id(timer_id id) |
65 |
{ |
66 |
hash_table_remove(timer_id_hash,&id); |
67 |
} |
68 |
|
69 |
/* |
70 |
* Select the queue of the pool that has the lowest criticity level. This |
71 |
* is a stupid method. |
72 |
*/ |
73 |
timer_queue_t *timer_select_queue_from_pool(void) |
74 |
{ |
75 |
timer_queue_t *s_queue,*queue; |
76 |
int level; |
77 |
|
78 |
/* to begin, select the first queue of the pool */ |
79 |
s_queue = timer_queue_pool; |
80 |
level = s_queue->level; |
81 |
|
82 |
/* walk through timer queues */ |
83 |
for(queue=timer_queue_pool->next;queue;queue=queue->next) { |
84 |
if (queue->level < level) { |
85 |
level = queue->level; |
86 |
s_queue = queue; |
87 |
} |
88 |
} |
89 |
|
90 |
/* returns selected queue */ |
91 |
return s_queue; |
92 |
} |
93 |
|
94 |
/* Add a timer in a queue */ |
95 |
static inline void timer_add_to_queue(timer_queue_t *queue, |
96 |
timer_entry_t *timer) |
97 |
{ |
98 |
timer_entry_t *t,*prev = NULL; |
99 |
|
100 |
/* Insert after the last timer with the same or earlier time */ |
101 |
for(t=queue->list;t;t=t->next) { |
102 |
if (t->expire > timer->expire) break; |
103 |
prev = t; |
104 |
} |
105 |
|
106 |
/* Add it in linked list */ |
107 |
timer->next = t; |
108 |
timer->prev = prev; |
109 |
timer->queue = queue; |
110 |
|
111 |
if (timer->next) |
112 |
timer->next->prev = timer; |
113 |
|
114 |
if (timer->prev) |
115 |
timer->prev->next = timer; |
116 |
else |
117 |
queue->list = timer; |
118 |
|
119 |
/* Increment number of timers in queue */ |
120 |
queue->timer_count++; |
121 |
|
122 |
/* Increment criticity level */ |
123 |
queue->level += timer->level; |
124 |
} |
125 |
|
126 |
/* Add a timer in a queue atomically */ |
127 |
static inline void timer_add_to_queue_atomic(timer_queue_t *queue, |
128 |
timer_entry_t *timer) |
129 |
{ |
130 |
TIMERQ_LOCK(queue); |
131 |
timer_add_to_queue(queue,timer); |
132 |
TIMERQ_UNLOCK(queue); |
133 |
} |
134 |
|
135 |
/* Remove a timer from queue */ |
136 |
static inline void timer_remove_from_queue(timer_queue_t *queue, |
137 |
timer_entry_t *timer) |
138 |
{ |
139 |
if (timer->prev) |
140 |
timer->prev->next = timer->next; |
141 |
else |
142 |
queue->list = timer->next; |
143 |
|
144 |
if (timer->next) |
145 |
timer->next->prev = timer->prev; |
146 |
|
147 |
timer->next = timer->prev = NULL; |
148 |
|
149 |
/* Decrement number of timers in queue */ |
150 |
queue->timer_count--; |
151 |
|
152 |
/* Decrement criticity level */ |
153 |
queue->level -= timer->level; |
154 |
} |
155 |
|
156 |
/* Remove a timer from a queue atomically */ |
157 |
static inline void |
158 |
timer_remove_from_queue_atomic(timer_queue_t *queue,timer_entry_t *timer) |
159 |
{ |
160 |
TIMERQ_LOCK(queue); |
161 |
timer_remove_from_queue(queue,timer); |
162 |
TIMERQ_UNLOCK(queue); |
163 |
} |
164 |
|
165 |
/* Free ressources used by a timer */ |
166 |
static inline void timer_free(timer_entry_t *timer,int take_lock) |
167 |
{ |
168 |
if (take_lock) TIMER_LOCK(); |
169 |
|
170 |
/* Remove ID from hash table */ |
171 |
hash_table_remove(timer_id_hash,&timer->id); |
172 |
|
173 |
if (take_lock) TIMER_UNLOCK(); |
174 |
|
175 |
/* Free memory used by timer */ |
176 |
free(timer); |
177 |
} |
178 |
|
179 |
/* Run timer action */ |
180 |
static inline int timer_exec(timer_entry_t *timer) |
181 |
{ |
182 |
return(timer->callback(timer->user_arg,timer)); |
183 |
} |
184 |
|
185 |
/* Schedule a timer in a queue */ |
186 |
static inline void timer_schedule_in_queue(timer_queue_t *queue, |
187 |
timer_entry_t *timer) |
188 |
{ |
189 |
m_tmcnt_t current,current_adj; |
190 |
|
191 |
/* Set new expiration date and clear "run" flag */ |
192 |
if (timer->flags & TIMER_BOUNDARY) { |
193 |
current_adj = m_gettime_adj(); |
194 |
current = m_gettime(); |
195 |
|
196 |
timer->expire = current + timer->offset + |
197 |
(timer->interval - (current_adj % timer->interval)); |
198 |
} else |
199 |
timer->expire += timer->interval; |
200 |
|
201 |
timer->flags &= ~TIMER_RUNNING; |
202 |
timer_add_to_queue(queue,timer); |
203 |
} |
204 |
|
205 |
/* Schedule a timer */ |
206 |
static int timer_schedule(timer_entry_t *timer) |
207 |
{ |
208 |
timer_queue_t *queue; |
209 |
|
210 |
/* Select the least used queue of the pool */ |
211 |
if (!(queue = timer_select_queue_from_pool())) { |
212 |
fprintf(stderr, |
213 |
"timer_schedule: no pool available for timer with ID %llu", |
214 |
timer->id); |
215 |
return(-1); |
216 |
} |
217 |
|
218 |
/* Reschedule it in queue */ |
219 |
TIMERQ_LOCK(queue); |
220 |
timer_schedule_in_queue(queue,timer); |
221 |
TIMERQ_UNLOCK(queue); |
222 |
return(0); |
223 |
} |
224 |
|
225 |
/* Timer loop */ |
226 |
static void *timer_loop(timer_queue_t *queue) |
227 |
{ |
228 |
struct timespec t_spc; |
229 |
timer_entry_t *timer; |
230 |
m_tmcnt_t c_time; |
231 |
|
232 |
/* We allow thread cancellation at any time */ |
233 |
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL); |
234 |
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL); |
235 |
|
236 |
/* Set signal properties */ |
237 |
m_signal_block(SIGINT); |
238 |
m_signal_block(SIGQUIT); |
239 |
m_signal_block(SIGTERM); |
240 |
|
241 |
for(;;) |
242 |
{ |
243 |
/* Prevent asynchronous access problems */ |
244 |
TIMERQ_LOCK(queue); |
245 |
|
246 |
/* Get first event */ |
247 |
timer = queue->list; |
248 |
|
249 |
/* |
250 |
* If we have timers in queue, we setup a timer to wait for first one. |
251 |
* In all cases, thread is woken up when a reschedule occurs. |
252 |
*/ |
253 |
if (timer) { |
254 |
t_spc.tv_sec = timer->expire / 1000; |
255 |
t_spc.tv_nsec = (timer->expire % 1000) * 1000000; |
256 |
pthread_cond_timedwait(&queue->schedule,&queue->lock,&t_spc); |
257 |
} |
258 |
else { |
259 |
/* We just wait for reschedule since we don't have any timer */ |
260 |
pthread_cond_wait(&queue->schedule,&queue->lock); |
261 |
} |
262 |
|
263 |
/* We need to check "running" flags to know if we must stop */ |
264 |
if (!queue->running) { |
265 |
TIMERQ_UNLOCK(queue); |
266 |
break; |
267 |
} |
268 |
|
269 |
/* |
270 |
* Now, we need to find why we were woken up. So, we compare current |
271 |
* time with first timer to see if we must execute action associated |
272 |
* with it. |
273 |
*/ |
274 |
c_time = m_gettime(); |
275 |
|
276 |
/* Get first event */ |
277 |
timer = queue->list; |
278 |
|
279 |
/* If there is nothing to do for now, wait again */ |
280 |
if ((timer == NULL) || (timer->expire > c_time)) { |
281 |
TIMERQ_UNLOCK(queue); |
282 |
continue; |
283 |
} |
284 |
|
285 |
/* |
286 |
* We have a timer to manage. Remove it from queue and mark it as |
287 |
* running. |
288 |
*/ |
289 |
timer_remove_from_queue(queue,timer); |
290 |
timer->flags |= TIMER_RUNNING; |
291 |
|
292 |
/* Execute user function and reschedule timer if required */ |
293 |
if (timer_exec(timer)) |
294 |
timer_schedule_in_queue(queue,timer); |
295 |
|
296 |
TIMERQ_UNLOCK(queue); |
297 |
} |
298 |
|
299 |
/* Stop thread immediately */ |
300 |
pthread_exit(NULL); |
301 |
return NULL; |
302 |
} |
303 |
|
304 |
/* Remove a timer */ |
305 |
int timer_remove(timer_id id) |
306 |
{ |
307 |
timer_queue_t *queue = NULL; |
308 |
timer_entry_t *timer; |
309 |
|
310 |
TIMER_LOCK(); |
311 |
|
312 |
/* Find timer */ |
313 |
if (!(timer = timer_find_by_id(id))) { |
314 |
TIMER_UNLOCK(); |
315 |
return(-1); |
316 |
} |
317 |
|
318 |
/* If we have a queue, remove timer from it atomically */ |
319 |
if (timer->queue) { |
320 |
queue = timer->queue; |
321 |
timer_remove_from_queue_atomic(queue,timer); |
322 |
} |
323 |
|
324 |
/* Release timer ID */ |
325 |
timer_free_id(id); |
326 |
|
327 |
/* Free memory used by timer */ |
328 |
free(timer); |
329 |
TIMER_UNLOCK(); |
330 |
|
331 |
/* Signal to this queue that it has been modified */ |
332 |
if (queue) |
333 |
pthread_cond_signal(&queue->schedule); |
334 |
return(0); |
335 |
} |
336 |
|
337 |
/* Enable a timer */ |
338 |
static timer_id timer_enable(timer_entry_t *timer) |
339 |
{ |
340 |
/* Allocate a new ID */ |
341 |
TIMER_LOCK(); |
342 |
timer->id = timer_alloc_id(); |
343 |
|
344 |
/* Insert ID in hash table */ |
345 |
if (hash_table_insert(timer_id_hash,&timer->id,timer) == -1) { |
346 |
TIMER_UNLOCK(); |
347 |
free(timer); |
348 |
return(0); |
349 |
} |
350 |
|
351 |
/* Schedule event */ |
352 |
if (timer_schedule(timer) == -1) { |
353 |
timer_free(timer,FALSE); |
354 |
timer = NULL; |
355 |
TIMER_UNLOCK(); |
356 |
return(0); |
357 |
} |
358 |
|
359 |
/* Returns timer ID */ |
360 |
TIMER_UNLOCK(); |
361 |
pthread_cond_signal(&timer->queue->schedule); |
362 |
return(timer->id); |
363 |
} |
364 |
|
365 |
/* Create a new timer */ |
366 |
timer_id timer_create_entry(m_tmcnt_t interval,int boundary,int level, |
367 |
timer_proc callback,void *user_arg) |
368 |
{ |
369 |
timer_entry_t *timer; |
370 |
|
371 |
/* Allocate memory for new timer entry */ |
372 |
if (!(timer = malloc(sizeof(*timer)))) |
373 |
return(0); |
374 |
|
375 |
timer->interval = interval; |
376 |
timer->offset = 0; |
377 |
timer->callback = callback; |
378 |
timer->user_arg = user_arg; |
379 |
timer->flags = 0; |
380 |
timer->level = level; |
381 |
|
382 |
/* Set expiration delay */ |
383 |
if (boundary) { |
384 |
timer->flags |= TIMER_BOUNDARY; |
385 |
} else |
386 |
timer->expire = m_gettime(); |
387 |
|
388 |
return(timer_enable(timer)); |
389 |
} |
390 |
|
391 |
/* Create a timer on boundary, with an offset */ |
392 |
timer_id timer_create_with_offset(m_tmcnt_t interval,m_tmcnt_t offset, |
393 |
int level,timer_proc callback,void *user_arg) |
394 |
{ |
395 |
timer_entry_t *timer; |
396 |
|
397 |
/* Allocate memory for new timer entry */ |
398 |
if (!(timer = malloc(sizeof(*timer)))) |
399 |
return(0); |
400 |
|
401 |
timer->interval = interval; |
402 |
timer->offset = 0; |
403 |
timer->callback = callback; |
404 |
timer->user_arg = user_arg; |
405 |
timer->flags = 0; |
406 |
timer->level = level; |
407 |
timer->flags |= TIMER_BOUNDARY; |
408 |
|
409 |
return(timer_enable(timer)); |
410 |
} |
411 |
|
412 |
/* Set a new interval for a timer */ |
413 |
int timer_set_interval(timer_id id,long interval) |
414 |
{ |
415 |
timer_queue_t *queue; |
416 |
timer_entry_t *timer; |
417 |
|
418 |
TIMER_LOCK(); |
419 |
|
420 |
/* Locate timer */ |
421 |
if (!(timer = timer_find_by_id(id))) { |
422 |
TIMER_UNLOCK(); |
423 |
return(-1); |
424 |
} |
425 |
|
426 |
queue = timer->queue; |
427 |
|
428 |
TIMERQ_LOCK(queue); |
429 |
|
430 |
/* Compute new expiration date */ |
431 |
timer->interval = interval; |
432 |
timer->expire = m_gettime() + (m_tmcnt_t)interval; |
433 |
|
434 |
timer_remove_from_queue(queue,timer); |
435 |
timer_schedule_in_queue(queue,timer); |
436 |
|
437 |
TIMERQ_UNLOCK(queue); |
438 |
TIMER_UNLOCK(); |
439 |
|
440 |
/* Reschedule */ |
441 |
pthread_cond_signal(&queue->schedule); |
442 |
return(0); |
443 |
} |
444 |
|
445 |
/* Create a new timer queue */ |
446 |
timer_queue_t *timer_create_queue(void) |
447 |
{ |
448 |
timer_queue_t *queue; |
449 |
|
450 |
/* Create new queue structure */ |
451 |
if (!(queue = malloc(sizeof(*queue)))) |
452 |
return NULL; |
453 |
|
454 |
queue->running = TRUE; |
455 |
queue->list = NULL; |
456 |
queue->level = 0; |
457 |
|
458 |
/* Create mutex */ |
459 |
if (pthread_mutex_init(&queue->lock,NULL)) |
460 |
goto error; |
461 |
|
462 |
/* Create condition */ |
463 |
if (pthread_cond_init(&queue->schedule,NULL)) |
464 |
goto error; |
465 |
|
466 |
/* Create thread */ |
467 |
if (pthread_create(&queue->thread,NULL,(void *(*)(void *))timer_loop,queue)) |
468 |
goto error; |
469 |
|
470 |
return queue; |
471 |
|
472 |
error: |
473 |
free(queue); |
474 |
return NULL; |
475 |
} |
476 |
|
477 |
/* Flush queues */ |
478 |
void timer_flush_queues(void) |
479 |
{ |
480 |
timer_entry_t *timer,*next_timer; |
481 |
timer_queue_t *queue,*next_queue; |
482 |
pthread_t thread; |
483 |
|
484 |
TIMER_LOCK(); |
485 |
|
486 |
for(queue=timer_queue_pool;queue;queue=next_queue) |
487 |
{ |
488 |
TIMERQ_LOCK(queue); |
489 |
next_queue = queue->next; |
490 |
thread = queue->thread; |
491 |
|
492 |
/* mark queue as not running */ |
493 |
queue->running = FALSE; |
494 |
|
495 |
/* suppress all timers */ |
496 |
for(timer=queue->list;timer;timer=next_timer) { |
497 |
next_timer = timer->next; |
498 |
timer_free_id(timer->id); |
499 |
free(timer); |
500 |
} |
501 |
|
502 |
TIMERQ_UNLOCK(queue); |
503 |
|
504 |
/* signal changes to the queue thread */ |
505 |
pthread_cond_signal(&queue->schedule); |
506 |
|
507 |
/* wait for thread to terminate */ |
508 |
pthread_join(thread,NULL); |
509 |
|
510 |
pthread_cond_destroy(&queue->schedule); |
511 |
pthread_mutex_destroy(&queue->lock); |
512 |
free(queue); |
513 |
} |
514 |
|
515 |
TIMER_UNLOCK(); |
516 |
} |
517 |
|
518 |
/* Add a specified number of queues to the pool */ |
519 |
int timer_pool_add_queues(int nr_queues) |
520 |
{ |
521 |
timer_queue_t *queue; |
522 |
int i; |
523 |
|
524 |
for(i=0;i<nr_queues;i++) |
525 |
{ |
526 |
if (!(queue = timer_create_queue())) |
527 |
return(-1); |
528 |
|
529 |
TIMER_LOCK(); |
530 |
queue->next = timer_queue_pool; |
531 |
timer_queue_pool = queue; |
532 |
TIMER_UNLOCK(); |
533 |
} |
534 |
|
535 |
return(0); |
536 |
} |
537 |
|
538 |
/* Initialize timer sub-system */ |
539 |
int timer_init(void) |
540 |
{ |
541 |
/* Initialize hash table which maps ID to timer entries */ |
542 |
if (!(timer_id_hash = hash_u64_create(TIMER_HASH_SIZE))) { |
543 |
fprintf(stderr,"timer_init: unable to create hash table."); |
544 |
return(-1); |
545 |
} |
546 |
|
547 |
/* Initialize default queues. If this fails, try to continue. */ |
548 |
if (timer_pool_add_queues(TIMERQ_NUMBER) == -1) { |
549 |
fprintf(stderr, |
550 |
"timer_init: unable to initialize at least one timer queue."); |
551 |
} |
552 |
|
553 |
return(0); |
554 |
} |