Linux 下定时器的实现方式分析

来源:developworks 作者:赵军
  
定时器属于基本的基础组件,不管是用户空间的程序开发,还是内核空间的程序开发,很多时候都需要有定时器作为基础组件的支持,但使用场景的不同,对定时器的实现考虑也不尽相同,本文讨论了在 Linux 环境下,应用层和内核层的定时器的各种实现方法,并分析了各种实现方法的利弊以及适宜的使用环境。

概论

定时器属于基本的基础组件,不管是用户空间的程序开发,还是内核空间的程序开发,很多时候都需要有定时器作为基础组件的支持,但使用场景的不同,对定时器的实现考虑也不尽相同,本文讨论了在 Linux 环境下,应用层和内核层的定时器的各种实现方法,并分析了各种实现方法的利弊以及适宜的使用环境。

首先,给出一个基本模型,定时器的实现,需要具备以下几个行为,这也是在后面评判各种定时器实现的一个基本模型 [1]:

StartTimer(Interval, TimerId, ExpiryAction)

注册一个时间间隔为 Interval 后执行 ExpiryAction 的定时器实例,其中,返回 TimerId 以区分在定时器系统中的其他定时器实例。

StopTimer(TimerId)

根据 TimerId 找到注册的定时器实例并执行 Stop 。

PerTickBookkeeping()

在一个 Tick 内,定时器系统需要执行的动作,它最主要的行为,就是检查定时器系统中,是否有定时器实例已经到期。注意,这里的 Tick 实际上已经隐含了一个时间粒度 (granularity) 的概念。

ExpiryProcessing()

在定时器实例到期之后,执行预先注册好的 ExpiryAction 行为。

上面说了基本的定时器模型,但是针对实际的使用情况,又有以下 2 种基本行为的定时器:

Single-Shot Timer

这种定时器,从注册到终止,仅仅只执行一次。

Repeating Timer

这种定时器,在每次终止之后,会自动重新开始。本质上,可以认为 Repeating Timer 是在 Single-Shot Timer 终止之后,再次注册到定时器系统里的 Single-Shot Timer,因此,在支持 Single-Shot Timer 的基础上支持 Repeating Timer 并不算特别的复杂。




基于链表和信号实现定时器 (2.4 版内核情况下 )

在 2.4 的内核中,并没有提供 POSIX timer [ 2 ]的支持,要在进程环境中支持多个定时器,只能自己来实现,好在 Linux 提供了 setitimer(2) 的接口。它是一个具有间隔功能的定时器 (interval timer),但如果想在进程环境中支持多个计时器,不得不自己来管理所有的计时器。 setitimer(2) 的定义如下:


清单 1. setitimer 的原型
#include <sys/time.h> 

 int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);

setitimer 能够在 Timer 到期之后,自动再次启动自己,因此,用它来解决 Single-Shot Timer 和 Repeating Timer 的问题显得很简单。该函数可以工作于 3 种模式:

ITIMER_REAL 以实时时间 (real time) 递减,在到期之后发送 SIGALRM 信号

ITIMER_VIRTUAL 仅进程在用户空间执行时递减,在到期之后发送 SIGVTALRM 信号

ITIMER_PROF 进程在用户空间执行以及内核为该进程服务时 ( 典型如完成一个系统调用 ) 都会递减,与 ITIMER_VIRTUAL 共用时可度量该应用在内核空间和用户空间的时间消耗情况,在到期之后发送 SIGPROF 信号

定时器的值由下面的结构定义:


清单 2. setitimer 定时器的值定义
struct itimerval { 
 struct timeval it_interval; /* next value */ 
 struct timeval it_value;     /* current value */ 
 }; 

 struct timeval { 
        long tv_sec;                /* seconds */ 
        long tv_usec;               /* microseconds */ 
 };

setitimer() 以 new_value 设置特定的定时器,如果 old_value 非空,则它返回 which 类型时间间隔定时器的前一个值。定时器从 it_value 递减到零,然后产生一个信号,并重新设置为 it_interval,如果此时 it_interval 为零,则该定时器停止。任何时候,只要 it_value 设置为零,该定时器就会停止。

由于 setitimer() 不支持在同一进程中同时使用多次以支持多个定时器,因此,如果需要同时支持多个定时实例的话,需要由实现者来管理所有的实例。用 setitimer() 和链表,可以构造一个在进程环境下支持多个定时器实例的 Timer,在一般的实现中的 PerTickBookkeeping 时,会递增每个定时器的 elapse 值,直到该值递增到最初设定的 interval 则表示定时器到期。

基于链表实现的定时器可以定义为:


清单 3. 基于链表的定时器定义
typedef int timer_id; 

 /** 
 * The type of callback function to be called by timer scheduler when a timer 
 * has expired. 
 * 
 * @param id                The timer id. 
 * @param user_data        The user data. 
 * $param len               The length of user data. 
 */ 
 typedef int timer_expiry(timer_id id, void *user_data, int len); 

 /** 
 * The type of the timer 
 */ 
 struct timer { 
        LIST_ENTRY(timer) entries;/**< list entry               */ 

        timer_id id;                /**< timer id                  */ 

        int interval;               /**< timer interval(second) */ 
        int elapse;                 /**< 0 -> interval             */ 

        timer_expiry *cb;          /**< call if expiry            */ 
        void *user_data;           /**< callback arg               */ 
        int len; 	                    /**< user_data length          */ 
 };

定时器的时间间隔以 interval 表示,而 elapse 则在 PerTickBookkeeping() 时递增,直到 interval 表示定时器中止,此时调用回调函数 cb 来执行相关的行为,而 user_data 和 len 为用户可以传递给回调函数的参数。

所有的定时器实例以链表来管理:


清单 4. 定时器链表
/** 
 * The timer list 
 */ 
 struct timer_list { 
        LIST_HEAD(listheader, timer) header;  /**< list header         */ 
        int num; 	                                   /**< timer entry number */ 
        int max_num;                               /**< max entry number    */ 

        void (*old_sigfunc)(int);                /**< save previous signal handler */ 
        void (*new_sigfunc)(int);                /**< our signal handler              */ 

        struct itimerval ovalue;                 /**< old timer value */ 
        struct itimerval value;                  /**< our internal timer value */ 
 };

这里关于链表的实现使用了 BSD 风格关于链表的一组宏,避免了再造轮子;该结构中,old_sigfunc 在 init_timer 初始定时器链表时候用来保存系统对 SIGALRM 的处理函数,在定时器系统 destory 时用来恢复到之前的处理函数; ovalue 的用途与此类似。


清单 5. 定时器链表的创建和 Destroy
/** 
 * Create a timer list. 
 * 
 * @param count  The maximum number of timer entries to be supported initially. 
 * 
 * @return        0 means ok, the other means fail. 
 */ 
 int init_timer(int count) 
 { 
        int ret = 0; 

        if(count <=0 || count > MAX_TIMER_NUM) { 
               printf("the timer max number MUST less than %d.\n", MAX_TIMER_NUM); 
               return -1; 
        } 

        memset(&timer_list, 0, sizeof(struct timer_list)); 
        LIST_INIT(&timer_list.header); 
        timer_list.max_num = count; 

        /* Register our internal signal handler and store old signal handler */ 
        if ((timer_list.old_sigfunc = signal(SIGALRM, sig_func)) == SIG_ERR) { 
                return -1; 
        } 
        timer_list.new_sigfunc = sig_func; 

     /*Setting our interval timer for driver our mutil-timer and store old timer value*/ 
        timer_list.value.it_value.tv_sec = TIMER_START; 
        timer_list.value.it_value.tv_usec = 0; 
        timer_list.value.it_interval.tv_sec = TIMER_TICK; 
        timer_list.value.it_interval.tv_usec = 0; 
        ret = setitimer(ITIMER_REAL, &timer_list.value, &timer_list.ovalue); 

        return ret; 
 } 


 /** 
 * Destroy the timer list. 
 * 
 * @return          0 means ok, the other means fail. 
 */ 
 int destroy_timer(void) 
 { 
        struct timer *node = NULL; 

        if ((signal(SIGALRM, timer_list.old_sigfunc)) == SIG_ERR) { 
                return -1; 
        } 

        if((setitimer(ITIMER_REAL, &timer_list.ovalue, &timer_list.value)) < 0) { 
                return -1; 
        } 

        while (!LIST_EMPTY(&timer_list.header)) {     /* Delete. */ 
		 node = LIST_FIRST(&timer_list.header); 
                LIST_REMOVE(node, entries); 
                /* Free node */ 
		 printf("Remove id %d\n", node->id); 
                free(node->user_data); 
                free(node); 
        } 

        memset(&timer_list, 0, sizeof(struct timer_list)); 

        return 0; 
 }

添加定时器的动作非常的简单,本质只是一个链表的插入而已:


清单 6. 向定时器链表中添加定时器
/** 
 * Add a timer to timer list. 
 * 
 * @param interval  The timer interval(second). 
 * @param cb  	    When cb!= NULL and timer expiry, call it. 
 * @param user_data Callback's param. 
 * @param len  	    The length of the user_data. 
 * 
 * @return          The timer ID, if == INVALID_TIMER_ID, add timer fail. 
 */ 
 timer_id  add_timer(int interval, timer_expiry *cb, void *user_data, int len) 
 { 
        struct timer *node = NULL; 

        if (cb == NULL || interval <= 0) { 
                return INVALID_TIMER_ID; 
        } 

	 if(timer_list.num < timer_list.max_num) { 
		 timer_list.num++; 
	 } else { 
		 return INVALID_TIMER_ID; 
	 } 

	 if((node = malloc(sizeof(struct timer))) == NULL) { 
		 return INVALID_TIMER_ID; 
	 } 
	 if(user_data != NULL || len != 0) { 
		 node->user_data = malloc(len); 
		 memcpy(node->user_data, user_data, len); 
		 node->len = len; 
	 } 

	 node->cb = cb; 
	 node->interval = interval; 
	 node->elapse = 0; 
	 node->id = timer_list.num; 

	 LIST_INSERT_HEAD(&timer_list.header, node, entries); 

    return node->id; 
 }

时间:2009-11-05 20:49 来源:developworks 作者:赵军 原文链接

好文,顶一下
(4)
100%
文章真差,踩一下
(0)
0%
------分隔线----------------------------


把开源带在你的身边-精美linux小纪念品
无觅相关文章插件,快速提升流量