您的位置:首页 > 产品设计 > UI/UE

ucgui timer实现分析

2011-08-31 18:58 162 查看
定时器属于基本的的基础组件,不管用户空间的程序开发,还是内核空间的程序开发,很多时候都需要有定时器作为基础组件的支持,但使用场景的不同,对定时器的实现考虑也不同。现在主要讨论UCGUI的Timer实现,以及与LINUX Timer的比较。

LINUX Timer的实现有基于链表和信号实现定时器、基于排序链表的定时器、基于文件描述符的定时器、基于时间轮(Timing-Wheel) 方式实现的定时器,而UCGUI是基于链表实现定时器。

首先,看LINUX基于链表和信号实现定时器是怎么实现的。

在 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;
                        }
 
注册的信号处理函数则用来驱动定时器系统:

清单 7. 信号处理函数驱动定时器
/* Tick Bookkeeping */
static void sig_func(int signo)
{
 struct timer *node = timer_list.header.lh_first;
 for ( ; node != NULL; node = node->entries.le_next)
{
     node->elapse++;
     if(node->elapse >= node->interval)
{
           node->elapse = 0;
           node->cb(node->id, node->user_data, node->len);
      }
 }
}
 
它主要是在每次收到 SIGALRM 信号时,执行定时器链表中的每个定时器 elapse 的自增操作,并与 interval 相比较,如果相等,代表注册的定时器已经超时,这时则调用注册的回调函数。
上面的实现,有很多可以优化的地方:考虑另外一种思路,在定时器系统内部将维护的相对 interval 转换成绝对时间,这样,在每PerTickBookkeeping 时,只需将当前时间与定时器的绝对时间相比较,就可以知道是否该定时器是否到期。这种方法,把递增操作变为了比较操作。并且上面的实现方式,效率也不高,在执行 StartTimer,StopTimer,PerTickBookkeeping 时,算法复杂度分别为 O(1),O(n),O(n),可以对上面的实现做一个简单的改进,在 StartTimer 时,即在添加 Timer 实例时,对链表进行排序,这样的改进,可以使得在执行 StartTimer,StopTimer,PerTickBookkeeping 时,算法复杂度分别为 O(n),O(1),O(1) 。
   UCGUI同样是基于链表实现,但不同的是它不处于操作系统层面,所以,其回调函数,不是只由信号来实现。
   首先,看看UCGUI的结构,跟LINUX一样,同样是有个链表结构来记录多个timer。
typedef struct {
  GUI_TIMER_CALLBACK* cb;
  GUI_TIMER_Handle hNext;
  int Flags;
         U32 Context;
         GUI_TIMER_TIME t0;
         GUI_TIMER_TIME Period;
} GUI_TIMER_Obj;
Cb为timer的callback function。Hnext则衔接各个timer,flags为标识,Context为上下文环境,相当于timer id
Link函数利用头插法,LINK起timer
最后,通过消息循环回调GUI_TIMER_Exec(void)函数

http://blog.csdn.net/jacklam200/article/details/5919876
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息