现在的位置: 首页 > 综合 > 正文

linux定时器

2018年04月03日 ⁄ 综合 ⁄ 共 5462字 ⁄ 字号 评论关闭

1.时间获取函数用于获得当前时间:

*time_t time(2) / time_t ()

* ftime(3) / struct timeb (毫秒)

* gettimeofday(2) / struct timeval (微秒)

* clock_gettime(2) / struct timespec (纳秒)

* gmtime / localtime / timegm / mktime / strftime / struct tm (这些与当前时间无关)

选择(计时)只使用 gettimeofday 来获取当前时间,原因在于

1. time 的精度太低,ftime 已被废弃,clock_gettime 精度最高,但是它系统调用的开销比 gettimeofday 大。

2. 在 x86-64 平台上,gettimeofday 不是系统调用,而是在用户态实现的(搜 vsyscall),没有上下文切换和陷入内核的开销。

3. gettimeofday 的分辨率 (resolution) 是 1 微秒,足以满足日常计时的需要。。

 

2.定时器

(1)简单可以用alarm(n)来实现(注意得用signal安装信号),但是只能精确到秒,然而我们如果需要用到更精准的怎么办?可以使用setitimer。函数原型如下:

struct itimerval {

struct timeval it_interval;

struct timeval it_value;

};

struct timeval {

long tv_sec;

long tv_usec;

};

it_interval指定间隔时间,it_value指定初始定时时间。如果只指定it_value,就是实现一次定时;如果同时指定 it_interval,则超时后,系统会重新初始化it_value为it_interval,实现重复定时;两者都清零,则会清除定时器

 int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue));

 setitimer()比alarm功能强大,支持3种类型的定时器:

    ITIMER_REAL :     以系统真实的时间来计算,它送出SIGALRM信号。

    ITIMER_VIRTUAL : 以该进程在用户态下花费的时间来计算,它送出SIGVTALRM信号。

    ITIMER_PROF : 以该进程在用户态下和内核态下所费的时间来计算,它送出SIGPROF信号。

setitimer()第一个参数which指定定时器类型(上面三种之一);第二个参数是结构itimerval的一个实例(成员为it_value.tv_sec和it_value.tv_usec);第三个参数可不做处理。setitimer()调用成功返回0,否则返回-1。

setitimer不会引起线程的阻塞、也不会引起线程的切换动作,就是简单的启动一个定时器,开始定时,而且这种定时应该是基于内核的,setitimer虽然有三种类型ITIMER_REAL,ITIMER_VIRTUAL ITIMER_PROF,但是在同一时间同一进程,一种类型的只能有1个setitimer;

 

 

(2)int timer_create(clockid_t clockid,  struct sigevent *evp, timer_t *timerid);

进程可以通过调用timer_create()创建特定的定时器,定时器是每个进程自己的,不是在fork时继承的。clock_id说明定时器是基于哪个时钟的,*timerid装载的是被创建的定时器的ID。该函数创建了定时器,并将他的ID 放入timerid指向的位置中。参数evp指定了定时器到期要产生的异步通知。如果evp为NULL,那么定时器到期会产生默认的信号,对 CLOCK_REALTIMER来说,默认信号就是SIGALRM。如果要产生除默认信号之外的其它信号,程序必须将 evp->sigev_signo设置为期望的信号码。struct sigevent 结构中的成员evp->sigev_notify说明了定时器到期时应该采取的行动。通常,这个成员的值为SIGEV_SIGNAL,这个值说明在定时器到期时,会产生一个信号。程序可以将成员evp->sigev_notify设为SIGEV_NONE来防止定时器到期时产生信号

此调用成功返回0,执行失败时,此调用会返回-1,不定义timerid,而且此调用会将errno设定会下面其中一个值:

EAGAIN

    系统的资源不足以完成此请求

EINVAL

    clockid指定了无效的POSIX时钟

ENOTSUP

    虽然clockid指定了有效的POSIX时钟,但是系统并不支持使用该时钟的定时器。POSIX保证所有实现均支持使用CLOCK_REALTIME时钟的定时器。至于是否支持其他时钟,则由实现自行决定。

 

clock_id取值为以下:

CLOCK_REALTIME :Systemwide realtime clock.

CLOCK_MONOTONIC:Represents monotonic time. Cannot be set.

CLOCK_PROCESS_CPUTIME_ID :High resolution per-process timer.

CLOCK_THREAD_CPUTIME_ID :Thread-specific timer.

CLOCK_REALTIME_HR :High resolution version of CLOCK_REALTIME.

CLOCK_MONOTONIC_HR :High resolution version of CLOCK_MONOTONIC.

 

  evp参数,如果非NULL值,用于定义当前定时器到期时所发生的异步通知。  

struct sigevent{

    union sigval sigev_value;

    int sigev_signo;

    int sigev_notify;

    void (*sigev_notify_function)(union sigval);

    pthread_attr_t *sigev_notify_attributes;

}; 

union sigval{

    int sival_int;

    void *sival_ptr;

};

通过将evp->sigev_notify设定为如下值来定制定时器到期后的行为:

SIGEV_NONE:什么都不做,只提供通过timer_gettime和timer_getoverrun查询超时信息。

SIGEV_SIGNAL: 当定时器到期,内核会将sigev_signo所指定的信号传送给进程。在信号处理程序中,si_value会被设定会sigev_value。

SIGEV_THREAD: 当定时器到期,内核会(在此进程内)以sigev_notification_attributes为线程属性创建一个线程,并且让它执行sigev_notify_function,传入sigev_value作为为一个参数。启动一个定时器

 

timer_create()所创建的定时器并未启动。要将它关联到一个到期时间以及启动时钟周期,可以使用timer_settime():

 int timer_settime( timer_t timerid, int flags, const struct itimerspec *value, struct itimerspect *ovalue);

 一次成功的timer_settime()调用会使用到期时间value(这时一个itimerspec结构)启动timerid所指定的定时器:

 struct itimespec{

    struct timespec it_interval;  /*next value*/

    struct timespec it_value;    /* current value */

};

  如同settimer(),it_value用于指定当前的定时器到期时间。当定时器到期,it_value的值会被更新成it_interval的值。如果it_interval的值为0,则定时器不是一个时间间隔定时器,一旦it_value到期就会回到未启动状态。

 timespec的结构提供了纳秒级分辨率:

struct timespec{

    time_t tv_sec; /*seconds*/

    long tv_nsec;   /*nanoseconds*/

};

  如果flags的值为TIMER_ABSTIME,则value所指定的时间值会被解读成绝对值(此值的默认的解读方式为相对于当前的时间)。这个经修改的行为可避免取得当前时间、计算“该时间”与“所期望的未来时间”的相对差额以及启动定时器期间造成竞争条件。

  如果ovalue的值不是NULL,则之前的定时器到期时间会被存入其所提供的itimerspec。如果定时器之前处在未启动状态,则此结构的成员全都会被设定成0。

  你可以在任何时刻由timer_gettime()取得一个定时器的到期时间而不会重置它:

int timer_gettime(timer_t timerid, struct itimerspec *value);

  一次成功的timer_gettime()调用会将timerid所指定的定时器的到期时间存入value所指定的结构并且返回0。执行失败时,此调用会返回-1并且将errno设定为下面的一个值:

EFAULT

    value是一个无效的指针

EINVAL

    timerid是一个无效的定时器

 

 

 取得一个定时器的超限运行次数

POSIX定义了一个接口用于确定指定定时器上发生超限运行的次数(如果有):

int timer_getoverrun(timer_t timerid);

  执行成功时,timer_getoverrun()会返回定时器初次到期与通知进程(例如通过信号)定时器已到期之间额外发生的定时器到期次数。举例来说,在我们之前的例子中,一个1ms的定时器运行了10ms,则此调用会返回9。如果超限运行的次数等于或大于DELAYTIMER_MAX,则此调用会返回DELAYTIMER_MAX。

执行失败时,此函数会返回-1并将errno设定会EINVAL,这个唯一的错误情况代表timerid指定了无效的定时器。

要删除一个定时器很容易:

 int timer_delete (timer_t timerid);

  一次成功的timer_delete()调用会销毁关联到timerid的定时器并且返回0。执行失败时,此调用会返回-1并将errno设定会EINVAL,这个唯一的错误情况代表timerid不是一个有效的定时器。

 

注意这种定时器在实际测试时发现有点问题,在虚拟机上运行没有任何反应?服务器上运行时,如间隔1s,打印时间为0,132ms,1132ms,。。。。。。。。。。。9132ms,9132ms。第一次居然是0s?其中132这个时间噪声不确定,也有可能使其他值,最后2个打印的时间值居然相同?总结:慎重使用此定时器。

 

 

(3)文件类定时器实现:

timerfd提供了如下接口供用户使用

timerfd_create

int timerfd_create(int clockid, int flags);

timerfd_create用于创建一个定时器文件。

参数clockid可以是CLOCK_MONOTONIC或者CLOCK_REALTIME。

参数flags可以是0或者O_CLOEXEC/O_NONBLOCK。

函数返回值是一个文件句柄fd。

timerfd_settime

int timerfd_settime(int ufd, int flags, const struct itimerspec * utmr, struct itimerspec * otmr);

此函数用于设置新的超时时间,并开始计时。

参数ufd是timerfd_create返回的文件句柄。

参数flags为1代表设置的是绝对时间;为0代表相对时间。

参数utmr为需要设置的时间。

参数otmr为定时器这次设置之前的超时时间。

函数返回0代表设置成功。

timerfd_gettime

int timerfd_gettime(int ufd, struct itimerspec * otmr);

此函数用于获得定时器距离下次超时还剩下的时间。如果调用时定时器已经到期,并且该定时器处于循环模式(设置超时时间时struct itimerspec::it_interval不为0),那么调用此函数之后定时器重新开始计时。

当timerfd为阻塞方式时,read函数将被阻塞,直到定时器超时。

函数返回值大于0,代表定时器超时;否则,代表没有超时(被信号唤醒,等等)。

 

定时器总结:基于信号类的定时器在多线程编程中会比较麻烦,而且很难定义多个定时器。而基于文件类型的定时器则可以完美的融合到select/poll 框架中,很方便创建多个定时器。

【上篇】
【下篇】

抱歉!评论已关闭.