Linux 系统上最常用的定时器是 setitmer 计时器
settimmmer原型:
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
which为定时器类型,setitimer支持3种类型的定时器: ITIMER_REAL: 以系统真实的时间来计算,它送出SIGALRM信号。 ITIMER_VIRTUAL: -以该进程在用户态下花费的时间来计算,它送出SIGVTALRM信号。 ITIMER_PROF: 以该进程在用户态下和内核态下所费的时间来计算,它送出SIGPROF信号。 setitimer()调用成功返回0,否则返回-1。
setitimer()第一个参数which指定定时器类型(上面三种之一);第二个参数是结构itimerval的一个实例;第三个参数可不做处理。
计时器值由以下结构定义:
struct itimerval { struct timeval it_interval; /* next value */ struct timeval it_value; /* current value */ }; struct timeval { time_t tv_sec; /* seconds */ suseconds_t tv_usec; /* microseconds */ };
it_interval指定间隔时间,it_value指定初始定时时间。如果只指定it_value,就是实现一次定时;如果同时指定 it_interval,则超时后,系统会重新初始化it_value为it_interval,实现重复定时;两者都清零,则会清除定时器。
tv_sec提供秒级精度,tv_usec提供微秒级精度,以值大的为先
示例代码:
#include <stdio.h> #include <sys/time.h> #include <string.h> #include <signal.h> #include <time.h> //简单打印信息,定时器触发函数 void print_info(int signo){ printf("timer fired\n"); } void init_sigaction(){ struct sigaction act; act.sa_handler = print_info; act.sa_flags = 0; sigemptyset(&act.sa_mask); sigaction(SIGPROF,&act,NULL); //设置信号 SIGPROF 的处理函数为 print_info } void init_time() { struct itimerval value; value.it_value.tv_sec=2; //定时器启动后,每隔2秒将执行相应的函数 value.it_value.tv_usec=0; value.it_interval=value.it_value; setitimer(ITIMER_PROF,&value,NULL); //初始化 timer,到期发送 SIGPROF 信号 } int main(int argc ,char *argv[]){ init_sigaction(); init_time(); while(1); return 0; }
编译输出:
这个程序使用 PROF 时间,每经过两秒 PROF 时间之后就会打印一下 timer fired 字符串。
需要指出:setitimer 计时器的精度为 ms,即 1000 分之 1 秒,足以满足绝大多数应用程序的需要。但多媒体等应用可能需要更高精度的定时,那么就需要考虑使用下一类定时器:POSIX Timer。
posix timer函数
最强大的定时器接口来自POSIX时钟系列,其创建、初始化以及删除一个定时器的行动被分为三个不同的函数:timer_create()(创建定时器)、timer_settime()(初始化定时器)以及 timer_delete()(销毁它)。POSIX Timer 对 setitimer 进行了增强,克服了 setitimer 的诸多问题:
- 1、一个进程同一时刻只能有一个 timer。假如应用需要同时维护多个 Interval 不同的计时器,必须自己写代码来维护。这非常不方便。使用 POSIX Timer,一个进程可以创建任意多个 Timer。
- 2、setitmer 计时器时间到达时,只能使用信号方式通知使用 timer 的进程,而 POSIX timer 可以有多种通知方式,比如信号,或者启动线程。
- 3、使用 setitimer 时,通知信号的类别不能改变:SIGALARM,SIGPROF 等,而这些都是传统信号,而不是实时信号,因此有 timer overrun 的问题;而 POSIX Timer 则可以使用实时信号。
- 4、 setimer 的精度是 ms,POSIX Timer 是针对有实时要求的应用所设计的,接口支持 ns 级别的时钟精度。
time_create[创建一个定时器]
time_create函数原型:
int timer_create(clockid_t clock_id, struct sigevent *evp, timer_t *timerid)
参数说明:
clock_id 说明定时器是基于哪个时钟的,*timerid 装载的是被创建的定时器的 ID。该函数创建了定时器,并将他的 ID 放入timerid指向的位置中。参数evp指定了定时器到期要产生的异步通知。clock_id取值为以下:
CLOCK_REALTIME :Systemwide realtime clock. //时间是系统保存的时间,即可以由 date 命令显示的时间,该时间可以重新设置。 CLOCK_MONOTONIC:Represents monotonic time. Cannot be set. CLOCK_PROCESS_CPUTIME_ID :High resolution per-process timer. //CLOCK_PROCESS_CPUTIME_ID 的含义与 setitimer 的 ITIMER_VIRTUAL 类似。 CLOCK_THREAD_CPUTIME_ID :Thread-specific timer.// CLOCK_THREAD_CPUTIME_ID 以线程为计时实体,当前进程中的某个线程真正地运行了一定时间才触发 Timer。 CLOCK_REALTIME_HR :High resolution version of CLOCK_REALTIME. CLOCK_MONOTONIC_HR :High resolution version of CLOCK_MONOTONIC.
如果evp为 NULL,那么定时器到期会产生默认的信号,对 CLOCK_REALTIMER来说,默认信号就是SIGALRM。如果要产生除默认信号之外的其它信号,程序必须将 evp->sigev_signo设置为期望的信号码。
struct sigevent 结构中的成员 evp->sigev_notify说明了定时器到期时应该采取的行动。通常,这个成员的值为SIGEV_SIGNAL,这个值说明在定时器到期时,会产生一个信号。程序可以将成员 evp->sigev_notify设为SIGEV_NONE来防止定时器到期时产生信号。
timer_settime[启动一个定时器]
int timer_settime(timer_t timerid, int flags, const struct itimerspec *value, struct itimerspect *ovalue); struct itimespec{ struct timespec it_interval; struct timespec it_value; }; struct timespec{ time_t tv_sec; long tv_nsec; };
参数说明:
如果flags的值为TIMER_ABSTIME,则value所指定的时间值会被解读成绝对值(此值的默认的解读方式为相对于当前的时间)。这个经修改的行为可避免取得当前时间、计算“该时间”与“所期望的未来时间”的相对差额以及启动定时器期间造成竞争条件。 如果ovalue的值不是NULL,则之前的定时器到期时间会被存入其所提供的itimerspec。如果定时器之前处在未启动状态,则此结构的成员全都会被设定成0。
//获得一个活动定时器的剩余时间 int timer_gettime(timer_t timerid,struct itimerspec *value); //取得一个定时器的超限运行次数 int timer_getoverrun(timer_t timerid);
有可能一个定时器到期了,而同一定时器上一次到期时产生的信号还处于挂起状态。在这种情况下,其中的一个信号可能会丢失。这就是定时器超限。程序可以通过调用timer_getoverrun来确定一个特定的定时器出现这种超限的次数。定时器超限只能发生在同一个定时器产生的信号上。由多个定时器,甚至是那些使用相同的时钟和信号的定时器,所产生的信号都会排队而不会丢失。
执行成功时,timer_getoverrun()会返回定时器初次到期与通知进程(例如通过信号)定时器已到期之间额外发生的定时器到期次数。
//删除一个定时器 int timer_delete (timer_t timerid);
一次成功的timer_delete()调用会销毁关联到timerid的定时器并且返回0。执行失败时,此调用会返回-1并将errno设定会 EINVAL,这个唯一的错误情况代表timerid不是一个有效的定时器。
POSIX timer到期通知方式
SIGEV_NONE 定时器到期时不产生通知。。。 SIGEV_SIGNAL 定时器到期时将给进程投递一个信号,sigev_signo 可以用来指定使用什么信号。 SIGEV_THREAD 定时器到期时将启动新的线程进行需要的处理 SIGEV_THREAD_ID(仅针对 Linux) 定时器到期时将向指定线程发送信号。
设置通知方式:
evp.sigev_value.sival_ptr = &timer; evp.sigev_notify = SIGEV_THREAD; evp.sigev_notify_function = handle; evp.sigev_value.sival_int = 3; //作为handle()的参数 ret = timer_create(CLOCK_REALTIME, &evp, &timer);
这里将通知方式设为 SIGEV_THREAD,handle为入口函数。然后设置定时器间隔,并启动 Timer:
ts.it_interval.tv_sec = 1; ts.it_interval.tv_nsec = 0; ts.it_value.tv_sec = 3; ts.it_value.tv_nsec = 0; ret = timer_settime(timer, TIMER_ABSTIME, &ts, NULL);
示例代码
#include <stdio.h> #include <sys/time.h> #include <string.h> #include <signal.h> #include <time.h> void handle(union sigval v){ time_t t; char p[32]; time(&t); strftime(p, sizeof(p), "%T", localtime(&t)); printf("%s thread %lu, val = %d, signal captured.\n", p, pthread_self(), v.sival_int); return; } int main(int argc, char *argv[]){ struct sigevent evp; struct itimerspec ts; timer_t timer; int ret; memset (&evp, 0, sizeof(evp)); evp.sigev_value.sival_ptr = &timer; evp.sigev_notify = SIGEV_THREAD; evp.sigev_notify_function = handle; evp.sigev_value.sival_int = 3; //作为handle()的参数 ret = timer_create(CLOCK_REALTIME, &evp, &timer); if( ret){ perror("timer_create"); } ts.it_interval.tv_sec = 1; ts.it_interval.tv_nsec = 0; ts.it_value.tv_sec = 3; ts.it_value.tv_nsec = 0; ret = timer_settime(timer, TIMER_ABSTIME, &ts, NULL); if( ret ) { perror("timer_settime"); } while(1); }
编译输出: