注册的信号处理函数则用来驱动定时器系统:
清单 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) 。改进后的定时器系统如下图 1:
图 1. 基于排序链表的定时器
基于 2.6 版本内核定时器的实现 (Posix 实时定时器 )
Linux 自 2.6 开始,已经开始支持 POSIX timer [ 2 ]所定义的定时器,它主要由下面的接口构成 :
清单 8. POSIX timer 接口
#include
#include
int timer_create(clockid_t clockid, struct sigevent *evp,
timer_t *timerid);
int timer_settime(timer_t timerid, int flags,
const struct itimerspec *new_value,
struct itimerspec * old_value);
int timer_gettime(timer_t timerid, struct itimerspec *curr_value);
int timer_getoverrun(timer_t timerid);
int timer_delete(timer_t timerid);
这套接口是为了让操作系统对实时有更好的支持,在链接时需要指定 -lrt 。
timer_create(2): 创建了一个定时器。
timer_settime(2): 启动或者停止一个定时器。
timer_gettime(2): 返回到下一次到期的剩余时间值和定时器定义的时间间隔。出现该接口的原因是,如果用户定义了一个 1ms 的定时器,可能当时系统负荷很重,导致该定时器实际山 10ms 后才超时,这种情况下,overrun=9ms 。
timer_getoverrun(2): 返回上次定时器到期时超限值。
timer_delete(2): 停止并删除一个定时器。
上面最重要的接口是 timer_create(2),其中,clockid 表明了要使用的时钟类型,在 POSIX 中要求必须实现 CLOCK_REALTIME 类型的时钟。 evp 参数指明了在定时到期后,调用者被通知的方式。该结构体定义如下 :