在编写应用程序的时候,经常需要用到定时器。根据使用情况,定时器的基本行为分为2种:Single-Shot Timer和Repeating Timer 。Single-Shot Timer 从注册到终止只执行一次。Repeating Timer每次终止后自动执行。
linux 在定时编程有以下几种接口:一是 setitimer ,二是Posix标准的 timer_create,三是timerfd_create。
linux内核2.4版本,不支持POSIX标准的timer_create接口。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 */
};
int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
int getitimer(int which, struct itimerval *curr_value);
该函数有一下三种工作模式:
TIMER_REAL 以实时时间 (real time) 递减,在到期之后发送 SIGALRM 信号
ITIMER_VIRTUAL 仅进程在用户空间执行时递减,在到期之后发送 SIGVTALRM 信号
ITIMER_PROF 进程在用户空间执行以及内核为该进程服务时 ( 如完成一个系统调用 ) 都会递减ITIMER_VIRTUAL 共用时可度量该应用在内核空间和用户空间的时间消耗情况,在到期之后发送 SIGPROF 信号
工作方式为:setitimer以new_value的值设置并启动定时器,如果old_value的值为非空,则返回定时器的which 类型时间间隔的前一个值。settimer从it_value开始递减到0,然后产生一个信号,并以it_interval的值设置定时器。如果it_interval的值为0,定时器停止。
setitimer不支持在进程中调用多次以使用多个定时器。settimer要在进程中支持多次个定时器,可以用链表和信号处理方式实现。基本思想是settimer定时为基本的单位如10ms, 每个定时器为链表的一个节点,每次超时,每个定时器的定时时间递,并检查是否到时,是的话就执行注册的定时处理函数。
与seitimer相比,linux2.6版本支持的POSIX标准timer_create函数可以在一个进程中创建多个定时器。主要接口如下:
#include <time.h>
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) */
};
struct itimerspec {
struct timespec it_interval; /* timer period */
struct timespec it_value; /* timer expiration */
};
<span style="font-size:12px;">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);</span>
clockid定义的定时器类型,分为以下几种:
CLOCK_REALTIME :墙上时钟,如果墙上时间被修改,定时时间将变化。如在8点10分设置定时1分钟,则8点11分定时到期,在定期超时之前如果墙上时间被修改为7点0分,则要等待71分钟才超时。
CLOCK_MONOTONIC:结束与开始的相对时间计算超时。
CLOCK_PROCESS_CPUTIME_ID:以进程占用的CPU时间计算超时
CLOCK_THREAD_CPUTIME_ID:以线程占用的CPU时间计算超时
工作方式为:timer_create创建定时器,timer_settime启动定时器。定时器超时后,以it_interval的值设置定时器,根据创建时设置的超时通知方式处理超时。如果it_interval的值为0,定时结束,否则开始下一次定时。定时器的启动和停止都是通过timer_settime实现。
sigev_notify的超时通知方式有:
SIGEV_NONE: 超时不以异步的方式通知。可以调用timer_gettime的方式获取定时器的状态,但是需要注意的是用这种方式检查定时器的超时,并不准确,在有可能在调用timer_gettime之前刚好超时已经过了,如果对定时精度不高如100ms级别可以使用。
SIGEV_SIGNAL:超时以信号的方式通知。每次超时发送一个sigev_signo指定的信号。超时时可以将sigev_value.sival_int设置为timerid的值,以此来区分多个定时器发送相同信号的情况。(如何实现?)
SIGEV_THREAD:超时时创建一个线程的方式通知。线程的属性通过sigev_notify_attributes设置。
POSIX的timer_create只适合在进程中使用,不适合在多线程下使用。为了支持在多线程下使用多个定时器,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函数和poll函数,并且支持fork和exec多进程语义。
参考资料:
Linux 下定时器的实现方式分析---赵军
http://www.ibm.com/developerworks/cn/linux/l-cn-timers/#5.%E5%9F%BA%E4%BA%8E%E6%97%B6%E9%97%B4%E8%BD%AE%28Timing-Wheel%29%E6%96%B9%E5%BC%8F%E5%AE%9E%E7%8E%B0%E7%9A%84%E5%AE%9A%E6%97%B6%E5%99%A8%7Coutline