【摘要】
本文主要介绍了linux的时间和定时器。
【关键词】
Linux 定时器 时间
一、问题的提出
我们开发程序时,经常会遇到时间和定时器的问题,为了更好的使用时间和定时器,现在列举一个一些时间结构体、函数和定时器。
二、解决思路
1.时间类型
1) time_t是一个长整型,一般用来表示用1970年以来的秒数。
2)struct timeval有两个成员,一个是秒,一个是微妙。
struct timeval
{
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
3) struct timespec有两个成员,一个是秒,一个是纳秒。
struct timespec
{
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
4) struct tm是直观意义上的时间表示方法
struct tm
{
int tm_sec; /* seconds */
int tm_min; /* minutes */
int tm_hour; /* hours */
int tm_mday; /* day of the month */ int tm_mon; /* month */
int tm_year; /* year */
int tm_wday; /* day of the week */ int tm_yday; /* day in the year */ int tm_isdst; /* daylight saving time */
};
5)
struct timeb
{
time_t time; //从1970来经过的秒数
unsigned short millitm; //毫秒
short timezone; //时区
short dstflag;//为日光节约时间的修正值,如果为非0代表启用日光节约修正
};
6)
struct timezone
{
int tz_minuteswest;//和格林尼治时间相差多少分
int tz_dsttime;//日光节约时间的状态
};
日光节约时间:夏时制。
2.时间函数
1) char *asctime(const struct tm *timeptr)
将时间和日期以字符串格式显示。
2)clock_t clock(void)
取得进程占用cpu的大约时间。
3)char *ctime(const time_t *timep)
将时间和日期以字符串格式显示。
4) double difftime(time_t time1, time_t time0)
计算时间time1和time0间的差距。
5) int ftime(struct timeb *tp)
取得目前的时间和日期。
6) int gettimeofday(struct timeval *tv, struct timezone *tz)
取得目前的时间。
7)strcut tm *gmtime(const time_t *timep)
time_t结构时间转tm结构时间,tm为UTC时区。
8) struct tm *localtime(const time_t *timep)
将time_t结构时间转tm结构时间,tm是当地时区。
9)time_t mktime(struct tm *timeptr)
将tm结构时间转换为time_t。
10) int settimeofday(const struct timeval *tv, const struct timezone *tz)
设置时间为tv,设置时区为tz。
11) size_t strftime(char *s, size_t max, const char *format, const struct tm *tm)
将参数tm的时间结构依照参数format所指定的字符串格式做转换,转换后的字符串将复制到参数s所指的字符串数组中,该字符串的最大长度为参数max所控制。
12) time_t time(time_t *t)
取得目前的时间,时间按照UTC。
13) void tzset(void)
从环境变量TZ取得目前当地的时间。
3.延迟函数
主要的延迟函数有:sleep(),usleep(),nanosleep(),select(),pselect().
1) unsigned int sleep(unsigned int seconds)
延时seconds秒。
2) void usleep(int micro_seconds)
延时 micro_seconds微妙
3) int nanosleep(const struct timespec *rqtp, struct timespec *rmtp)
延时的时间为rqtp,如果nansleep被信号中断,且rmtp不为NULL,则rmtp指定的位置上包含的就是剩余时间。
4) int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
如果将readfds, writefds, exceptfds置为NULL,timeout为非零,则延时timeout。
5) int pselect(int maxfdp1, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec tsptr, const sigset_t *sigmask)
如果将readfds, writefds, exceptfds置为NULL,tsptr为非零,则延时tsptr。
3.定时器
1) unsigned int alarm(unsigned int seconds)
设置一个定时器,在seconds秒后超时,当定时器超时时,产生SIGALRM信号。如果不忽略或不捕捉此信号,则其默认动作是终止调用该alarm函数的进程,如果设置了SIGALRM信号处理函数,则会执行该信号处理函数。
每个进程只能有一个闹钟时钟。如果在调用alarm时,以前已为该进程设置过闹钟时钟,而且它还没有超时,则将该闹钟时钟的余留值作为本次alarm函数调用的值返回,以前登记的闹钟时钟则被新值代替。
例子:
void sigalrm_fn(int sig)
{
printf("alarm!/n");
alarm(2);
return;
}
int main(void)
{
signal(SIGALRM, sigalrm_fn);
alarm(1);
while(1) pause();
}
2) POSIX:XSI间隔定时器
int getitimer(int which, struct itimerval *value);
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
struct itimerval
{
struct timeval it_interval; /* 计时器重启动的间歇值 */
struct timeval it_value; /* 计时器安装后首先启动的初始值 */
};
getitimer()用计时器的当前值填写value指向的结构体。
setitimer()将value指向的结构体设为计时器的当前值,如果ovalue不是NULL,将返回计时器原有值。
which:间歇计时器类型,有三种选择:
l ITIMER_REAL //数值为0,计时器的值实时递减,发送的信号是SIGALRM。
l ITIMER_VIRTUAL //数值为1,进程执行时递减计时器的值(虚拟时间),发送的信号是SIGVTALRM。
l ITIMER_PROF //数值为2,虚拟时间和进程的系统时间递减,发送的信号是SIGPROF。
itimerval结构中的it_value是减少的时间,当这个值为0的时候就发出相应的信号了. 然后再将it_value设置为it_interval值. 也就是先处理it_value中设置的值,为0后发送信号(根据which来判断发送什么信号),之后都是根据it_interval的值发送信号。若it_value和it_interval都为0,也就没有相应的信号产生了。
和alarm()一样,由于信号的限制,POSIX:XSI间隔定时器对于每个进程来说最多只有3个定时器。
例子:
void sigroutine(int signo)
{
switch (signo)
{
case SIGALRM:
printf("Catch a signal -- SIGALRM /n");
signal(SIGALRM, sigroutine);
break;
case SIGVTALRM:
printf("Catch a signal -- SIGVTALRM /n");
signal(SIGVTALRM, sigroutine);
break;
}
return;
}
int main()
{
struct itimerval value, ovalue, value2;
sec = 5;
printf("process id is %d/n", getpid());
signal(SIGALRM, sigroutine);
signal(SIGVTALRM, sigroutine);
value.it_value.tv_sec = 1;
value.it_value.tv_usec = 0;
value.it_interval.tv_sec = 1;
value.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &value, &ovalue);
value2.it_value.tv_sec = 0;
value2.it_value.tv_usec = 500000;
value2.it_interval.tv_sec = 0;
value2.it_interval.tv_usec = 500000;
setitimer(ITIMER_VIRTUAL, &value2, &ovalue);
for(;;)
;
}
3)POSIX:TMR间隔定时器
int timer_create(clockid_t clock_id, struct sigevent *evp, timer_t *timerid)
进程可以通过调用timer_create()创建特定的定时器,定时器是每个进程自己的,不是在fork时继承的。timer_create的参数clock_id说明定时器是基于哪个时钟的,*timerid装载的是被创建的定时器的ID。timer_create函数创建了定时器,并将他的ID放入timerid指向的位置中。参数evp指定了定时器到期要产生的异步通知。如果evp为NULL,那么定时器到期会产生默认的信号,对CLOCK_REALTIMER来说,默认信号就是SIGALRM。对那些定时器到期时要产生除默认信号之外的其它信号的定时器来说,程序必须将evp->sigev_signo设置为期望的信号码。struct sigevent 结构中的成员evp->sigev_notify说明了定时器到期时应该采取的行动。通常,这个成员的值为SIGEV_SIGNAL,这个值说明在定时器到期时,会产生一个信号。程序可以将成员evp->sigev_notify设为SIGEV_NONE来防止定时器到期时产生信号。
如果几个定时器产生了同一个信号,处理程序可以用evp->sigev_value来区分是哪个定时器产生了信号。要实现这种功能,程序必须在为信号安装处理程序时,使用struct sigaction的成员sa_flags中的标志符SA_SIGINFO。
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.
struct sigevent
{
int sigev_notify; //notification type
int sigev_signo; //signal number
union sigval sigev_value; //signal value
void (*sigev_notify_function)(union sigval);
pthread_attr_t *sigev_notify_attributes;
}
union sigval
{
int sival_int; //integer value
void *sival_ptr; //pointer value
}
通过将evp->sigev_notify设定为如下值来定制定时器到期后的行为:
l SIGEV_SIGNAL: 发送由evp->sigev_sino指定的信号到调用进程,evp->sigev_value的值将被作为siginfo_t结构体中si_value的值。
l SIGEV_NONE:什么都不做,只提供通过timer_gettime和timer_getoverrun查询超时信息。
l SIGEV_THREAD: 以evp->sigev_notification_attributes为线程属性创建一个线程,在新建的线程内部以evp->sigev_value为参数调用evp->sigev_notification_function。
int timer_delete(timer_t timerid);
删除ID为timerid的POSIX:TMR定时器。
int timer_settime(timer_t timerid,int flags,const struct itimerspec *value,struct itimerspec *ovalue);
struct itimerspec
{
struct timespec it_interval; //定时器周期值
struct timespec it_value; //定时器到期值
};
timer_settime负责启动或停止timer_create创建的定时器。参数flag说明定时器使用的是相对时间还是绝对时间。相对时间与POSIX:XSI定时器使用的策略类似,而绝对时间的精确度更高。参数vaule指向的值来设置timerid指定的定时器。如果ovalue不为NULL,timer_settime就将定时器以前的值放在ovalue指定的位置上。如果定时器正在运行,那么*ovalue的成员it_value非零,并包含了定时器到期之前剩余的时间。
TIMER_ABSTIME表示绝对时间;如果flag没有设定为TIMER_ABSTIME,则定时器从调用开始在it_value内超时;如果设定为TIMER_ABSTIME,该函数表现为时间直到下一次超时被设定为it_value指定的绝对时间和与timerid相联的时钟值的差值。定时器的再装由value的it_interval成员值来设定。
int timer_gettime(timer_t timerid,struct itimerspec *value);
获得一个活动定时器的剩余时间。
int timer_getoverrun(timer_t timerid);
有可能一个定时器到期了,而同一定时器上一次到期时产生的信号还处于挂起状态。在这种情况下,其中的一个信号可能会丢失。这就是定时器超限。程序可以通过调用timer_getoverrun来确定一个特定的定时器出现这种超限的次数。定时器超限只能发生在同一个定时器产生的信号上。由多个定时器,甚至是那些使用相同的时钟和信号的定时器,所产生的信号都会排队而不会丢失。
例子:
编译方法:
gcc -o example example.c -lrt -lpthread
那个rt库就是POSIX realtime extension的库。
#include <stdio.h>
#include <time.h>
#include <signal.h>
void
handle (union sigval v)
{
time_t t;
char p[32];
time (&t);
strftimep, sizeof (p), "%T", localtime (&t));
printf("%s thread %lu, val = %d, signal captured./n", p, pthread_self(), v.sival_int);
return;
}
int create (int seconds, int id)
{
timer_t tid;
struct sigevent se;
struct itimerspec ts, ots;
memset (&se, 0, sizeof (se));
se.sigev_notify = SIGEV_THREAD;
se.sigev_notify_function = handle;
se.sigev_value.sival_int = id; //作为handle()的参数
if (timer_create (CLOCK_REALTIME, &se, &tid) < 0)
{
perror ("timer_creat");
return -1;
}
puts ("timer_create successfully.");
ts.it_value.tv_sec = 3;
ts.it_value.tv_nsec = 0;
ts.it_interval.tv_sec = seconds;
ts.it_interval.tv_nsec = 0;
if (timer_settime (tid, TIMER_ABSTIME, &ts, &ots) < 0)
{
perror ("timer_settime");
return -1;
}
return 0;
}
int main (void)
{
create (3, 1);
create (5, 2);
for (;;)
{
sleep (10);
}
}
三、总结
Linux下定时器主要使用上面介绍的三种定时器。