时间间隔定时器
interval timer(时间间隔定时器)系统调用自从被POSIX标准化后,首次出现于4.2BSD,能够提供比alarm()还多的控制:
#include
int getitimer(int which, struct itimerval *value);
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
interval timer的运作如同alarm(),但是可以选择自动重新启动它们,而且可运作在下面其中一种模式之中:
ITIMER_REAL
测量真是事件 。当所指定的真实时间值已过去,内核会送出一个SIGALARM信号给进程
ITIMER_VIRTUAL
只会在进程的用户空间程序代码执行时递减其值。当所选定的进程时间过去后,内核会送出一个SIGVTALRM信号给进城。
ITIMER_PROF
会在进城执行时以及内核替进程服务时(例如进行系统调用)递减其值。当所指定的时间量过去后,内核会送出一个SIGPROF信号给进城。此模式常会与ITIMER_VIRTUAL结合,让程序可以测量进程所耗用的用户时间与内核时间。
ITIMER_REAL所测量的时间如同alarm(),另两种模式则可用于概要分析。
itimerval结构允许用户指定时间量直到定时器到期为止以及指定到底时间,这让你能够以指定的到期时间重新启动定时器:
struct itimerval{
struct timeval it_interval; /*next value*/
struct timeval it_value; /*current value*/
};
如稍早所述,timeval结构可以提供微秒级分辨率:
struct timeval{
long tv_sec; /*seconds*/
long tv_usec; /*microseconds*/
};
setitimer()会使用it_value所指定的到期时间来启动一个which类型的定时器。一旦it_value所指定的时间过去后,内核会使用it_interval所提供的时间重新启动该定时器。因此,it_value是当前定时器剩下的时间。一旦it_value的值为0时,它会被设定为it_interval。如果定时器到期,而且it_interval的值为0,则不会重新启动该定时器。同样地,如果一个活动中的定时器的it_value被设为0,则定时器会停止运行,而且不会被重新启动。
如果ovalue的值不是NULL,则which类型的时间间隔定时器先前的值会被返回。
getitimer()会返回which类型的时间间隔定时器当前的值。
执行成功时,这两个调用都会返回0;发生错误时,则会返回-1,在此情况下errno会被设定会下面其中一个值 :
EFAULT
value或ovalue是一个无效的指针
EINVAL
which不是应该ieyouxiao的时间间隔定时器类型。
下面的程序代码片段会创建一个SIGALRM信号初期程序,接着会以5秒的初始到期时间来启动一个时间间隔定时器,随之而来的是1秒的时间间隔:
有些Unix系统会通过SIGALRM来实现sleep()与usleep(),显然,alarm()与setitimer()会使用SIGALRM。因此,程序设计者必须小心不要重复调用这些函数,否则结果是未定义的。如果只是短暂等待,程序设计者应该使用nanosleep(),按照POSIX的规定将不会使用信号。如果使用定时器,程序设计者应该使用setitimer()或alarm()。
高级定时器
最强大的定时器接口来自POSIX时钟系列
如果使用的是POSIX基于时钟的定时器,则创建、初始化以及删除一个定时器的行动被分为三个不同的函数:timer_create()(创建定时器)、timer_settime()(初始化定时器)以及timer_delete(销毁它)。
创建一个定时器
timer_create()可用于创建一个定时器:
#include
#include
int timer_create(clockid_t clockid, struct sigevent *evp, timer_t *timerid);
以此成功的timer_create()调用会创建一个与POSIX时钟clockid相关联的新定时器,将独一无二的定时器标识符存入timerid并且返回0。此调用只会替定时器的运行设置环境,实际上任何事情都不会发生,直到定时器被启动,如下一节所示。
下面的范例会创建一个新的定时器CLOCK_PROCESS_CPUTIME_ID的POSIX时钟,以将定时器的标识符存入timer:
timer_t timer;
intret;
ret = timer_create(CLOCK_PROCESS_CPUTIME_ID, NULL, &timer);
if(ret)
perror("timer_create");
执行失败时,此调用会返回-1,不定义timerid,而且此调用会将errno设定会下面其中一个值:
EAGAIN
系统的资源不足以完成此请求
EINVAL
clockid指定了无效的POSIX时钟
ENOTSUP
虽然clockid指定了有效的POSIX时钟,但是系统并不支持使用该时钟的定时器。POSIX保证所有实现均支持使用CLOCK_REALTIME时钟的定时器。至于是否支持其他时钟,则由实现自行决定。
evp参数,如果非NULL值,用于定义当前定时器到期时所发生的异步通知。此结构定义于头文件中,它的内容对程序设计者而言应该是不透明的,但是它至少具有以下字段:
#include
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;
};
当一个定时器到期时,相较于内核通知进程的方式,POSIX基于时钟的定时器可以提供更多的控制权,它允许进程指定内核将送出何种信号,甚至允许内核派生一个现成,并且执行一个函数以响应定时器到期的事实。一个进程可通过sigev_notify指定当定时器到期的行为,它必须是下面其中一个值:
SIGEV_NONE
一个空的通知。当定时器到期,不会有任何事发生
SIGEV_SIGNAL
当定时器到期,内核会将sigev_signo所指定的信号传送给进程。在信号处理程序中,si_value会被设定会sigev_value。
SIGEV_THREAD
当定时器到期,内核会(在此进程内)派生一个新的线程,并且让它执行sigev_notify_function,传入sigev_value作为为一个参数。从此函数返回后,线程会终止。如果sigev_notify_attributes的值不是NULL,则所提供的pthread_attr_t结构用于定义新线程的行为。
如果evp的值是NULL(就像我们前面的例子),则所设置的定时器到期通知宛如sigev_notify的值是SIGEV_SIGNAL、sigev_signo的值是SIGALRM以及sigev_value的值是定时器的标识符。因此,在默认情况下,这些定时器的通知方式类似于POSIX的时间间隔定时器。然而,通过自定义,它们可以做的事情还有很多、很多!
下面的例子会创建一个机遇CLOCK_REALTIME的定时器。当定时器到期时,内核将送出SIGUSR1信号并把si_value设定成存储定时器标识符的地址:
structsigevent evp;
timer_t timer;
intret;
evp.sigev_value.sival_ptr = &timer;
evp.sigev_notify = SIGEV_SIGNAL;
evp.sigev_signo = SIGUSR1;
ret = timer_create(CLOCK_REALTIME, &evp, &timer);
if( ret)
perror("timer_create");
启动一个定时器
timer_create()所创建的定时器并未启动。要将它关联到一个到期时间以及启动时钟周期,可以使用timer_settime():
#include
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_create()初始化的timer值,下面的例子可以创建一个每秒到期一次的周期性定时器:
structitimerspec ts;
intret;
ts.it_interval.tv_sec = 1;
ts.it_interval.tv_nsec = 0;
ts.it_value.tv_sec = 1;
ts.it_value.tv_nsec = 0;
ret = timer_settime(timer, 0, &ts, NULL);
if( ret )
perror("timer_settime");
取得一个定时器的到期时间
你可以在任何时刻由timer_gettime()取得一个定时器的到期时间而不会重置它:
#include
int timer_gettime(timer_t timerid, struct itimerspec *value);
一次成功的timer_gettime()调用会将timerid所指定的定时器的到期时间存入value所指定的结构并且返回0。执行失败时,此调用会返回-1并且将errno设定为下面的一个值:
EFAULT
value是一个无效的指针
EINVAL
timerid是一个无效的定时器
例如:
structitimerspec ts;
intret;
ret = timer_gettime(timer,&ts_get);
if( ret )
perror("timer_gettime");
else
{
printf("current sec = %ld, nsec=%ld/n", ts_get.it_value.tv_sec, ts_get.it_value.tv_nsec);
printf("next sec = %ld, nsec=ld/n", ts_get.it_interval.tv_sec, ts_get.it_interval.tv_nsec);
}
取得一个定时器的超限运行次数
POSIX定义了一个接口用于确定指定定时器上发生超限运行的次数(如果有):
#include
int timer_getoverrun(timer_t timerid);
执行成功时,timer_getoverrun()会返回定时器初次到期与通知进程(例如通过信号)定时器已到期之间额外发生的定时器到期次数。举例来说,在我们之前的例子中,一个1ms的定时器运行了10ms,则此调用会返回9。
如果超限运行的次数等于或大于DELAYTIMER_MAX,则此调用会返回DELAYTIMER_MAX。
执行失败时,此函数会返回-1并将errno设定会EINVAL,这个唯一的错误情况代表timerid指定了无效的定时器。
例如:
intret;
ret = timer_getoverrun(timer);
if( ret == -1)
perror("timer_getoverrun");
elseif(ret == 0)
printf("no overrun/n");
else
printf("%d overrun(s)/n", ret);
删除一个定时器
要删除一个定时器很容易:
#include
int timer_delete (timer_t timerid);
一次成功的timer_delete()调用会销毁关联到timerid的定时器并且返回0。执行失败时,此调用会返回-1并将errno设定会EINVAL,这个唯一的错误情况代表timerid不是一个有效的定时器。
自己写的小程序:
每隔5秒定时器就程序发出一个SIGALRM信号, 程序接收到这个信号后打印一个调试信息
#include
#include
#include
#include
#include
#include
#define TIMER_ENABLE 1 /* 时钟开启 */
#define DEBUGP(format,args...) fprintf(stdout, format, ##args) /* 调试信息打印 */
static void print_str(char *s) {
while(1) {
printf("%sn", s);
sleep(1);
}
}
static void sigtimer() {
DEBUGP("It's signal!n");
}
static void set_timer(int flag) {
struct itimerval tv;
memset(&tv, 0, sizeof(tv));
tv.it_interval.tv_sec = tv.it_value.tv_sec = flag ? 5 : 0;
tv.it_interval.tv_usec = tv.it_value.tv_usec = 0;
setitimer(ITIMER_REAL, &tv, NULL); /* ITIMER_REAL 返回SIGALARM信号给进程 */
}
int main(int argc, char **argv) {
if(argc < 2) {
printf("argv error!n");
exit(0);
}
/* 定义信号触发函数 */
signal(SIGALRM, sigtimer);
/* 初始化定时器 */
set_timer(TIMER_ENABLE);
print_str(argv[1]);
return 0;
}