http://www.cnblogs.com/my_life/articles/5310361.html
http://www.ibm.com/developerworks/cn/linux/l-cn-timers/
http://blog.csdn.net/walkingman321/article/details/6101536
http://m.oschina.net/blog/222287
http://www.cnblogs.com/zhanghairong/p/3757656.html
定时器的实现,需要具备以下几个行为,这也是在后面评判各种定时器实现的一个基本模型 [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
基于链表和信号实现定时器 (2.4 版内核情况下 )
在 2.4 的内核中,并没有提供 POSIX timer [ 2 ]的支持,要在进程环境中支持多个定时器,只能自己来实现,好在 Linux 提供了 setitimer(2) 的接口。它是一个具有间隔功能的定时器 (interval timer),但如果想在进程环境中支持多个计时器,不得不自己来管理所有的计时器。 setitimer(2) 的定义如下:
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 信号
由于 setitimer() 不支持在同一进程中同时使用多次以支持多个定时器,因此,如果需要同时支持多个定时实例的话,需要由实现者来管理所有的实例。用 setitimer() 和链表,可以构造一个在进程环境下支持多个定时器实例的 Timer
基于 2.6 版本内核定时器的实现 (Posix 实时定时器 )
Linux 自 2.6 开始,已经开始支持 POSIX timer [ 2 ]所定义的定时器,它主要由下面的接口构成 :
清单 8. POSIX timer 接口
#include <signal.h>
#include <time.h>
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 。
清单 9. POSIX timer 接口中的信号和事件定义
union sigval { int sival_int; void *sival_ptr; }; struct sigevent { int sigev_notify; /* Notification method */ int sigev_signo; /* Timer expiration signal */ union sigval sigev_value; /* Value accompanying signal or passed to thread function */ void (*sigev_notify_function) (union sigval); /* Function used for thread notifications (SIGEV_THREAD) */ void *sigev_notify_attributes; /* Attributes for notification thread (SIGEV_THREAD) */ pid_t sigev_notify_thread_id; /* ID of thread to signal (SIGEV_THREAD_ID) */ };
其中,sigev_notify 指明了通知的方式 :
SIGEV_NONE
当定时器到期时,不发送异步通知,但该定时器的运行进度可以使用 timer_gettime(2) 监测。
SIGEV_SIGNAL
当定时器到期时,发送 sigev_signo 指定的信号。
SIGEV_THREAD
当定时器到期时,以 sigev_notify_function 开始一个新的线程。该函数使用 sigev_value 作为其参数,当 sigev_notify_attributes 非空,则制定该线程的属性。注意,由于 Linux 上线程的特殊性,这个功能实际上是由 glibc 和内核一起实现的。
SIGEV_THREAD_ID (Linux-specific)
仅推荐在实现线程库时候使用。
如果 evp 为空的话,则该函数的行为等效于:sigev_notify = SIGEV_SIGNAL,sigev_signo = SIGVTALRM,sigev_value.sival_int = timer ID 。
由于 POSIX timer [ 2 ]接口支持在一个进程中同时拥有多个定时器实例,所以在上面的基于 setitimer() 和链表的 PerTickBookkeeping 动作就交由 Linux 内核来维护,这大大减轻了实现定时器的负担。由于 POSIX timer [ 2 ]接口在定时器到期时,有更多的控制能力,因此,可以使用实时信号避免信号的丢失问题,并将 sigev_value.sival_int 值指定为 timer ID,这样,就可以将多个定时器一起管理了。需要注意的是,POSIX timer [ 2 ]接口只在进程环境下才有意义 (fork(2) 和 exec(2) 也需要特殊对待 ),并不适合多线程环境。与此相类似的,Linux 提供了基于文件描述符的相关定时器接口:
清单 10. Linux 提供的基于文件描述符的定时器接口
#include <sys/timerfd.h> int timerfd_create(int clockid, int flags); int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value); int timerfd_gettime(int fd, struct itimerspec *curr_value);
这样,由于基于文件描述符,使得该接口可以支持 select(2),poll(2) 等异步接口,使得定时器的实现和使用更加的方便,更重要的是,支持 fork(2),exec(2) 这样多进程的语义,因此,可以用在多线程环境之中,它们的使用比 POSIX timer [ 2 ]更加的灵活,其根本原因在于定时器的管理统一到了 unix/linux 基本哲学之一 ---- “一切皆文件”之下。
最小堆实现的定时器
基于时间轮 (Timing-Wheel) 方式实现的定时器
如果需要支持的定时器范围非常的大,时间轮的实现方式则不能满足这样的需求。
因为这样将消耗非常可观的内存,假设需要表示的定时器范围为:0 – 2^31ticks,则简单时间轮需要 2^32 个元素空间,这对于内存空间的使用将非常的庞大。也许可以降低定时器的精度,使得每个 Tick 表示的时间更长一些,但这样的代价是定时器的精度将大打折扣。现在的问题是,度量定时器的粒度,只能使用唯一粒度吗?想想日常生活中常遇到的水表,如下图 :
:
在上面的水表中,为了表示度量范围,分成了不同的单位,比如 1000,100,10 等等,相似的,表示一个 32bits 的范围,也不需要 2^32 个元素的数组。实际上,Linux 的内核把定时器分为 5 组,每组的粒度(对应水表例子的单位)分别表示为:1 jiffies,256 jiffies,256*64 jiffies,256*64*64 jiffies,256*64*64*64 jiffies,每组中桶的数量分别为:256,64,64,64,64,能表示的范围为 2^32 。有了这样的实现,驱动内核定时器的机制也可以通过水表的例子来理解了,就像水表,每个粒度上都有一个指针指向当前时间,时间以固定 tick 递增,而当前时间指针则也依次递增,如果发现当前指针的位置可以确定为一个注册的定时器,就触发其注册的回调函数。 Linux 内核定时器本质上是 Single-Shot Timer,如果想成为 Repeating Timer,可以在注册的回调函数中再次的注册自己。以下是实现代码:
http://blog.csdn.net/walkingman321/article/details/6101536
常用的定时器实现算法有两种:红黑树和时间轮(timing wheel)。
在Linux2.6的代码中,kernel/timer.c文件实现了一个通用定时器机制,使用的是时间轮算法。
每一个CPU都有一个struct tvec_base结构,代表这个CPU使用的时间轮。
struct tvec_base
{
spinlock_t lock; // 同步锁
struct timer_list * running_timer; // 当前正在运行的定时器
unsigned long timer_jiffies; // 当前运行到的jiffies
struct tvec_root tv1; //个位
struct tvec tv2; //十位
struct tvec tv3; //百位
struct tvec tv4; //千位
struct tvec tv5;
}
struct tvec_root与struct tvec都是数组,数组中的每一项都指定一个链表。struct tvec_root定义的数组大小是256(2的8次方);struct tvec_root定义的数组大小是64(2的6次方)。所以,tv1~6定义的数组总大小是2的(8 + 4*6 = 32)次方,正好对应32位处理器中jiffies的定义(unsigned long)。
因为使用的是wheel算法,tv1~5就代表5个wheel。
tv1是转速最快的wheel,所有在256个jiffies内到期的定时器都会挂在tv1的某个链表头中。
tv2是转速第二快的wheel,里面挂的定时器超时jiffies在2^8 ~ 2^(8+6)之间。
tv3是转速第三快的wheel,超时jiffies在2^(8+6) ~ 2^(8+2*6)之间。
tv4、tv5类似。
http://www.cnblogs.com/zhanghairong/p/3757656.html