转载自:http://www.cnblogs.com/feisky/archive/2010/03/20/1690561.html
使用定时器的目的无非是为了周期性的执行某一任务,或者是到了一个指定时间去执行某一个任务。要达到这一目的,一般有两个常见的比较有效的方法。
一个是用linux内部的三个定时器:ITIME_REAL,ITIMER_VIRTUAL,ITIME_PROF;
另一个是用sleep, usleep函数让进程睡眠一段时间;
其实,还有一个方法,那就是用gettimeofday(), difftime()等自己来计算时间间隔,然后时间到了就执行某一任务,但是这种方法效率低,所以不常用。
1. 系统提供的定时器
linux操作系统为每一个进程提供的3个内部计时器:
ITIMER_REAL: 给一个指定的时间间隔,按照实际的时间来减少这个计数,当时间间隔为0的时候发出SIGALRM信号
ITIMER_VIRTUAL: 给定一个时间间隔,当进程执行的时候才减少计数,时间间隔为0的时候发出SIGVTALRM信号
ITIMER_PROF: 给定一个时间间隔,当进程执行或者是系统为进程调度的时候,减少计数,时间到了,发出SIGPROF信号,这个和ITIMER_VIRTUAL联合,常用来计算系统内核时间和用户时间。
用到的函数有:
int getitimer (int which ,struct itimerval * value ) ;
int setitimer (int which ,struct itimerval * newvalue ,struct itimerval * oldvalue ) ;
用到的数据结构有:
strcut timeval
{
long tv_sec ; /*秒*/
long tv_usec ; /*微秒*/
} ;
struct itimerval
{
struct timeval it_interval ;/*时间间隔*/
struct timeval it_value ; /*当前时间计数*/
} ;
it_interval用来指定每隔多长时间执行任务, it_value用来保存当前时间离执行任务还有多长时间。
比如说, 你指定it_interval为2秒(微秒为0),开始的时候我们把it_value的时间也设定为2秒(微秒为0),当过了一秒, it_value就减少一个为1, 再过1秒,则it_value又减少1,变为0,这个时候发出信号(告诉用户时间到了,可以执行任务了),并且系统自动把it_value的时间重置为it_interval的值,即2秒,再重新计数。
struct sigaction
{
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
其中,sa_handler是一个指针,指向信号处理函数;
与后面两种实现方式的主要区别:
由于 定时器时系统自身提供的,所以时间到了之后,消息也是由 系统自动传给消息队列的;
而后两种由于是用户自己实现,所以必须将进程ID和信号ID传送到消息队列(通过sigqueue()函数来实现)。
使用步骤:
1. 初始化定时器的类型和时间间隔;
2. 初始化信号的类型和回调函数;
3. 设定时间到了后,回调函数执行;
示例:
#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
#include <string.h>
#include <unistd.h>
//信号回调函数
void sign_proc(int signo)
{
printf("time is running out\n");
}
//建立信号处理机制
void init_sigaction()
{
struct sigaction signact;
//指定有信号之后的回调函数
signact.sa_handler=sign_proc;
signact.sa_flags=0;
//初始化信号集
sigemptyset(&signact.sa_mask);
//指定信号类型,注意与定时器类型保持一致
sigaction(SIGALRM, &signact, NULL);
}
//设置定时器
void init_time()
{
struct itimerval interval;
//初始值
interval.it_value.tv_sec=5;
interval.it_value.tv_usec=0;
//间隔时间
interval.it_interval=interval.it_value;
//设置定时器类型为:ITIMER_REAL
setitimer(ITIMER_REAL, &interval, NULL);
}
int main()
{
init_time();
init_sigaction();
while(1);
return 0;
}
说明:程序设定定时器类型为实时定时器:ITMER_REAL,每隔5秒钟都会发送一个SIGALRM信号,当主函数接收到了这个信号之后,调用信号处理函数sign_proc处理。
对于ITIMER_VIRTUAL和ITIMER_PROF的使用方法类似,当你在setitimer里面设置的定时器为ITIMER_VIRTUAL的时候,你把sigaction里面的SIGALRM改为SIGVTALARM, 同理,ITIMER_PROF对应SIGPROF。
2. 通过sleep以及usleep实现定时
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
void show_msg(int signo)
{
printf("time is using up\n");
}
int main()
{
struct sigaction signact;
union sigval tsval;
//设定信号的回调函数
signact.sa_handler=show_msg;
signact.sa_flags=0;
//初始化信号集
sigemptyset(&signact.sa_mask);
sigaction(50, &signact, NULL);
while(1)
{
sleep(2);
sigqueue(getpid(), 50, tsval);
}
return 0;
}
看到了吧,这个要比上面的简单多了,而且你用秒表测一下,时间很准,指定2秒到了就给你输出一个字符串。所以,如果你只做一般的定时,到了时间去执行一个任务,这种方法是最简单的。
3. 通过自己计算时间差的方法来定时
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
void sign_proc(int signo)
{
printf("time is running out\n");
}
int main()
{
struct sigaction signact;
union sigval tsval;
//设置信号变量
signact.sa_handler=sign_proc;
signact.sa_flags=0;
sigemptyset(&signact.sa_mask);
sigaction(50, &signact, NULL);
time_t oldtime;
time(&oldtime);
while(1)
{
time_t nowtime;
time(&nowtime);
if(nowtime-oldtime>=2)
{
//将进程ID,信号ID放到消息机制中
sigqueue(getpid(), 50, tsval);
oldtime=nowtime;
}
}
}
这种做法效率比较低,一般很少使用。
这个和上面不同之处在于,是自己手工计算时间差的,如果你想更精确的计算时间差,你可以把 time 函数换成gettimeofday,这个可以精确到微妙。
上面介绍的几种定时方法各有千秋,在计时效率上、方法上和时间的精确度上也各有不同,采用哪种方法,就看你程序的需要。