IoTShare

timerfd简介

1. 简介

timerfd的由来比较简单,我们以往在使用定时器时,需要设定超时时间和超时后的回调函数,在定时器时间到来时,我们所注册的回调函数将会被执行。但是这种定时器很难被epoll等I/O多路复用机制接管。于是Linux下的timefd应运而生,使用timerfd创建出的定时器是基于文件描述符进行管理的,在达到超时时间时,描述符将置为可读,并可以从中读取到超时次数(启动定时器后或上次read之后的超时次数)。

Linux提供如下三个系统调用去使用timerfd。如果曾经使用过Linux下的定时器,这三个系统调用和timer_create, timer_settime以及timer_gettime类似。

#include <sys/timerfd.h>

int timerfd_create(int clockid, int flags);
 
int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);

int timerfd_gettime(int fd, struct itimerspec *curr_value);

下面分别进行介绍。

timerfd_create

创建一个定时器对象,其中clockid用来指定时钟的类型,比如常见的CLOCK_REALTIME和单调时钟CLOCK_MONOTONICflags主要对这个定时器描述符进行设置。TFD_NONBLOCK用来设置描述符在进行操作时是否阻塞;TFD_CLOEXEC用来设置当进行exec族操作时,对应的描述符进行关闭。

timerfd_settime

用来对定时器行为进行一些配置,执行这个函数后,定时器将开始工作。
其中flags对定时器的执行做一些配置,有TFD_TIMER_ABSTIMETFD_TIMER_CANCEL_ON_SET
TFD_TIMER_ABSTIME:定时器溢出时间当做绝对时间,也就是如果更改系统时间早于这个时间,定时器将立即执行;
TFD_TIMER_CANCEL_ON_SET:更改系统时间时,定时器将取消。

new_value参数主要对定时器的周期进行设置,类型为struct itimerspec

struct itimerspec {
    struct timespec it_interval;  /* Interval for periodic timer */
    struct timespec it_value;     /* Initial expiration */
};

it_interval是设置定时器的执行周期,也就是定时器每隔多久会触发一次;
it_value是设置定时器初始的溢出时间,也就是第一次设置后多久会触发一次;

old_value参数如果不为空,将返回上次定时器设置的new_value参数。

timerfd_gettime

这个函数相对比较简单,返回当前定时器对象配置的时间函数。

使用样例

这里我采用man手册中的一个样例来说明如何使用timerfd
这个样例所做的事情,就是根据程序的入参,来创建一个定时器,并设置响应的定时器第一次溢出时间定时器间隔以及最大的溢出次数

       #include <sys/timerfd.h>
       #include <time.h>
       #include <unistd.h>
       #include <inttypes.h>      /* Definition of PRIu64 */
       #include <stdlib.h>
       #include <stdio.h>
       #include <stdint.h>        /* Definition of uint64_t */

       #define handle_error(msg) \
               do { perror(msg); exit(EXIT_FAILURE); } while (0)

       static void
       print_elapsed_time(void)
       {
           static struct timespec start;
           struct timespec curr;
           static int first_call = 1;
           int secs, nsecs;

           if (first_call) {
               first_call = 0;
               if (clock_gettime(CLOCK_MONOTONIC, &start) == -1)
                   handle_error("clock_gettime");
           }

           if (clock_gettime(CLOCK_MONOTONIC, &curr) == -1)
               handle_error("clock_gettime");

           secs = curr.tv_sec - start.tv_sec;
           nsecs = curr.tv_nsec - start.tv_nsec;
           if (nsecs < 0) {
               secs--;
               nsecs += 1000000000;
           }
           printf("%d.%03d: ", secs, (nsecs + 500000) / 1000000);
       }

       int
       main(int argc, char *argv[])
       {
           struct itimerspec new_value;
           int max_exp, fd;
           struct timespec now;
           uint64_t exp, tot_exp;
           ssize_t s;

           if ((argc != 2) && (argc != 4)) {
               fprintf(stderr, "%s init-secs [interval-secs max-exp]\n",
                       argv[0]);
               exit(EXIT_FAILURE);
           }

           if (clock_gettime(CLOCK_REALTIME, &now) == -1)
               handle_error("clock_gettime");

           /* Create a CLOCK_REALTIME absolute timer with initial
              expiration and interval as specified in command line. */

           new_value.it_value.tv_sec = now.tv_sec + atoi(argv[1]);
           new_value.it_value.tv_nsec = now.tv_nsec;
           if (argc == 2) {
               new_value.it_interval.tv_sec = 0;
               max_exp = 1;
           } else {
               new_value.it_interval.tv_sec = atoi(argv[2]);
               max_exp = atoi(argv[3]);
           }
           new_value.it_interval.tv_nsec = 0;

           fd = timerfd_create(CLOCK_REALTIME, 0);
           if (fd == -1)
               handle_error("timerfd_create");

           if (timerfd_settime(fd, TFD_TIMER_ABSTIME, &new_value, NULL) == -1)
               handle_error("timerfd_settime");

           print_elapsed_time();
           printf("timer started\n");

           for (tot_exp = 0; tot_exp < max_exp;) {
               s = read(fd, &exp, sizeof(uint64_t));
               if (s != sizeof(uint64_t))
                   handle_error("read");

               tot_exp += exp;
               print_elapsed_time();
               printf("read: %" PRIu64 "; total=%" PRIu64 "\n", exp, tot_exp);
           }

           exit(EXIT_SUCCESS);
       }

我们执行后会观察如下现象

当我们按下ctrl+Z时,程序将在后台挂起。过十几秒之后我们使用fg命令将后台的程序调度到前台继续执行,会发现继续运行时read第一次读取到的个数是11次,也说明了readtimerfd描述符中读取到的确实是定时器的溢出次数,这个定时器是个内核定时器,因为当我们程序挂起时,这个定时器其实还在工作。

本原创文章未经允许不得转载 | 当前页面:IoTShare » timerfd简介

评论