Linux 定时器 setitimer
阅读了《Unix/Linux系统编程》中关于定时器及时钟服务的部分,结合网上资料进行了整理
1. 相关概念
基于X86架构的个人计算机有数个定时器,包括实时时钟RTC、可编程间隔定时器PIT、多核CPU中的本地定时器、高分辨率定时器。
实时时钟RTC
:RTC由一个小型备用电池供电,即使计算机关机时,它也能够连续运行。RTC用于实时提供时间和日期信息。由于RTC在电脑关机时继续运行,因此可以解释为什么开机后系统显示的时间与现实中保持一致。时间变量是一个长整数,即从1970年1月1日起经过的秒数。
2. 时钟相关系统调用与库函数
2.1 gettimeofday系统调用
返回当前时间即当前秒数和微秒数。其中秒数是相对于1970年1月1日0点所经过的秒数。
int gettimeofday(struct timeval * tv, struct timezone * tz)
-
参数tv指向一个timeval结构体变量,该变量保存返回的时间结果。
timeval结构体定义如下:tv_sec成员保存秒数,tv_usec成员保存微秒数
struct timeval { __time_t tv_sec; /* Seconds. */ __suseconds_t tv_usec; /* Microseconds. */ };
-
第二个参数类型timezone已过期,使用NULL即可。
2.2 settimeofday系统调用
设置系统时间
int settimeofday(const struct timeval *tv, const struct timezone *tz)
- 第一个参数tv即为要设置的系统时间
- 第二个参数类型timezone已过期,使用NULL即可
2.3 ctime库函数
以日历形式显示当前日期和时间。
char * ctime(const time_t * timer)
- 参数是指向 time_t 变量的指针,该变量即为从1970年开始的秒数。
- 返回值是一个C字符串,该字符串以日历形式表示当前日期和时间。
示例:获取当前时间并以日历形式显示
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <time.h>
int main(){
struct timeval t;
gettimeofday(&t,NULL);//获取当前系统时间
printf("cur time is : %s",ctime(&t.tv_sec));//以日历形式打印当前时间
}
运行结果
[email protected]:~/桌面/linux_study/section5$ gcc test.c
[email protected]:~/桌面/linux_study/section5$ ./a.out
cur time is : Sat Jun 19 10:38:22 2021
2.4 time系统调用
以秒为单位返回当前时间(从1970年到现在的秒数)。该系统调用缺点是时间精度是秒而不是微秒
time_t time(time_t * timer)
- 参数timer:如果该参数不是NULL,除了返回当前时间外,还将当前时间存储在timer中
- 返回值:当前时间
2.5 clock函数
返回程序执行起(一般为程序的开头)到调用该函数时,占用CPU的时间(时钟计时单元)
clock_t clock(void)
- 返回值是自程序启动起,处理器时钟所使用的时间。如果失败,则返回 -1 值。
在time.h文件中,还定义了一个常量CLOCKS_PER_SEC
,用来表示一秒钟会有多少个时钟计时单元
#define CLOCKS_PER_SEC ((clock_t) 1000000)
在Linux系统中,CLOCKS_PER_SEC
表示的值可能不同。这里该宏定义表示1000000
,因此每个时钟计时单元表示一微秒,即程序执行一微妙,clock函数的返回值就+1。
因此,为了获取 CPU 所使用的秒数,需要除以 CLOCKS_PER_SEC。
clock_t c = clock();//获取该程序从开始到现在占用CPU的时间(时钟计时单元)
printf("%f\n",(double)c/CLOCKS_PER_SEC);//打印出占用CPU的秒数
需要注意,clock返回的是占用CPU的时间,因此如果进程不是处于运行态(即不占用CPU时),那么该时间不算在clock返回值内
示例:进程sleep 5秒,然后打印占用CPU时间
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>
#include <time.h>
int main(){
sleep(5);
clock_t c = clock();
printf("%f\n",(double)c/CLOCKS_PER_SEC);
}
运行结果:可以看出该进程占用CPU时间仅有0.000618秒,即sleep的时间并不算在clock函数返回值内,因为进程sleep时不占用CPU。
[email protected]:~/桌面/linux_study/section5$ gcc test.c
[email protected]:~/桌面/linux_study/section5$ ./a.out
0.000618
2.6 times系统调用
用于获取进程的具体执行时间,将结果保存在tms类型变量中
clock_t times(struct tms * buffer)
-
参数buffer:保存进程具体执行时间,tms结构体类型:
struct tms{ clock_t tms_utime; /* User CPU time. 进程在用户模式下花费 CPU 时间*/ clock_t tms_stime; /* System CPU time. 进程在系统模式(内核)下花费 CPU 时间 */ clock_t tms_cutime; /* User CPU time of dead children. 已死掉子进程在用户模式下花费 CPU 时间*/ clock_t tms_cstime; /* System CPU time of dead children. 已死掉子进程在系统(内核)模式下花费 CPU 时间*/ };
-
返回值:返回自过去某个任意时间点以来所经过的时钟刻度的数量。 返回值可能溢出clock_t类型的时钟数。 出错时,返回-1,并适当设置 errno。
2.7 间隔定时器
Linux为每个进程提供了三种不同类型的间隔计时器,可用作进程计时。
通过setitimer系统调用创建间隔定时器
2.7.1 setitimer系统调用
创建间隔定时器。通过which参数指定不同种类的定时器,当间隔定时器到期时,会向进程发送一个信号(即为发送给进程进行处理的一个数字1-31),并将定时器重置为指定的间隔值
int setitimer(int which, const struct itimerval * new_value, struct itimerval * old_value)
-
参数which:间隔定时器类型。总共有三种定时器类型
-
ITIMER_REAL
:实时定时器,以系统真实的时间来计算,到期时它发出SIGALRM(14)信号。 -
ITIMER_VIRTUAL
:以该进程在用户态下花费的CPU时间来计算,它发出SIGVTALRM(26)信号。 -
ITIMER_PROF
:以该进程在用户态下和内核态下所费的CPU时间来计算。它发出SIGPROF(27)信号。
-
-
参数new_value:指向itimerval结构体变量,用来对计时器进行设置。
struct itimerval { /* 定时器过期时放入'it_value'的值(定时器过期时重置的下一次间隔时长)。 */ struct timeval it_interval; /* 当前时刻距离定时器下一次到期的时长(初始定时时间)。 */ struct timeval it_value; };
其中timeval结构体:
struct timeval { __time_t tv_sec; /* 秒 */ __suseconds_t tv_usec; /* 微秒 */ };
setitimer工作机制是,先对it_value倒计时,当it_value为零时触发信号。然后重置为it_interval。继续对it_value倒计时。一直这样循环下去。
如果 new_value.it_value 中的任一字段不为零,则定时器在指定时间初始到期。 如果 new_value.it_value 中的两个字段都为零,则定时器被解除。因此使用setitimer系统调用创建定时器时new_value.it_value中的两个字段不能都是0,不然就解除定时器了。
如果只指定it_value,即it_interval为零it_value大于0,仅仅会延时而不会定时(也就是说仅仅会触发一次信号)。
-
参数old_value:指向itimerval结构体变量。通常使用不上,即设置为NULL。用来存储上一次setitimer调用时设置的new_value值。
2.7.2 getitimer系统调用
函数getitimer()将由which指定的定时器的当前值放入cur_value指向的变量。
int getitimer(int __which, struct itimerval *cur_value)
cur_value.it_value是距离下一次到期还有多少时间。 这个值随着定时器的倒计时而变化,并在定时器到期时被重置为 it_interval。 如果it_value的两个字段都是零,那么这个定时器目前是被解除的(不活动)。
cur_value.it_interval为定时器的间隔值。如果it_interval的两个字段都是0,那么这是一个单次定时器(也就是说,它只延时一次,而不会定时间隔)。
2.7.3 间隔定时器示例
创建实时定时器
#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
#include <time.h>
//信号处理函数
void func(int sig)
{
printf("signal : %d\n",sig);
}
int main()
{
signal(SIGALRM, func);//注册信号处理函数
struct itimerval new_value;
new_value.it_value.tv_sec = 1;//初始延时1秒
new_value.it_value.tv_usec = 0;
new_value.it_interval.tv_sec = 2;//定时器间隔2秒
new_value.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &new_value, NULL);//创建定时器
while(1);
}
需要注意ITIMER_REAL定时器无论进程是否正在执行都进行定时。而另外两种定时器ITIMER_VIRTUAL和ITIMER_PROF仅在占用CPU时才进行定时(即进程处于运行态时才计时)。如下面的例子:
创建ITIMER_PROF定时器,并将进程sleep 5秒后进入while(1)死循环
#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
void func(int sig)
{
printf("signal : %d\n",sig);
}
int main(int argc, char *argv[])
{
signal(SIGPROF, func);
struct itimerval new_value;
new_value.it_value.tv_sec = 0;
new_value.it_value.tv_usec = 1;//初始延时1微秒
new_value.it_interval.tv_sec = 0;
new_value.it_interval.tv_usec = 500000;//定时器间隔0.5s
printf("begin timer\n");
setitimer(ITIMER_PROF, &new_value, NULL);
sleep(5);//sleep 5秒
printf("5 seconds left \n");
while(1);
}
运行结果:可见进程在sleep时由于不占用CPU所以不会使得ITIMER_PROF计时
[email protected]-vmpc:~/桌面/linux_study/section5$ gcc test.c
[email protected]-vmpc:~/桌面/linux_study/section5$ ./a.out
begin timer
5 seconds left
signal : 27
signal : 27
signal : 27
signal : 27
signal : 27