欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux 定时器 setitimer

程序员文章站 2022-06-24 21:38:00
...

阅读了《Unix/Linux系统编程》中关于定时器及时钟服务的部分,结合网上资料进行了整理

1. 相关概念

基于X86架构的个人计算机有数个定时器,包括实时时钟RTC、可编程间隔定时器PIT、多核CPU中的本地定时器、高分辨率定时器。

实时时钟RTCRTC由一个小型备用电池供电,即使计算机关机时,它也能够连续运行。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