Linux中timerfd系列函数使用指南

timerfd_create, timerfd_settime, timerfd_gettime系列函数将定时器的实现与文件描述符绑定在一起,定时器超时的那一刻文件描述符变得可读,因此可以很好的与 select、poll 和 epoll 结合在一起使用。

timerfd_create 系统调用将创建一个定时器并与一个文件描述符进行关联。
timerfd_settime 启动或停止一个定时器。
timerfd_gettime 用于获取距下一个到期的定时器的时间信息。

#include <sys/timerfd.h>

/*
	timerfd_create 调用成功,返回一个文件描述符;
	调用失败,返回-1, errno被设置为具体的错误码
*/
int timerfd_create(int clockid, int flags);


/*
	timerfd_settime 和 timerfd_gettime 调用成功,返回0;
	调用失败,返回-1,errno被设置为具体的错误码
*/
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 参数用于指定定时器类型,必须指定为下列参数值中的一个。参数值的具体含义,这里不赘述,请使用man手册查看。

参数值 描述
CLOCK_REALTIME
CLOCK_MONOTONIC
CLOCK_BOOTTIME (Since Linux 3.15)
CLOCK_REALTIME_ALARM (since Linux 3.11)
CLOCK_BOOTTIME_ALARM (since Linux 3.11)

clockid 常用的参数值为 CLOCK_REALTIMECLOCK_MONOTONIC
CLOCK_REALTIME 表示的测量真实时间(即挂钟 wall-clock)的全系统时钟。
CLOCK_MONOTONIC 表示定时器使用的时钟是一个单调递增的时钟,不受系统时间的调整影响 。(man clock_gettime)

flags 参数可以为 TFD_NONBLOCKTFD_CLOEXEC 的或运算。

timerfd_settime

fd 参数表示 timerfd_create 创建返回的文件描述符。

new_value 参数用于指定定时器的过期时间,old_value 如果不为空,用于获取调用 timerfd_settime 时的计数器设置,具体见下面 timerfd_gettime 的描述。

new_valueold_value 使用结构体 itimerspec 表示,该结构体类型如下所示:

struct timespec {
   time_t tv_sec;                /* Seconds */
   long   tv_nsec;               /* Nanoseconds */
};

struct itimerspec {
   struct timespec it_interval;  /* 定时器的周期间隔 */
   struct timespec it_value;     /* 初始过期时间 */
};

对于参数 new_valuenew_value.it_value 设置了定时器的初始过期时间,若new_value.it_value 的两个字段都被设置为0,则表示暂停该定时器;new_value.it_interval 表示定时器的时间间隔,若new_value.it_interval 的两个字段参数都为0,则表示该定时器只会过期一次,否则,定时器每重复 new_value.it_interval 的时间间隔过期一次。

默认情况下(flags 设置为0),定时器使用相对时间计时,即从调用 timerfd_settime 设置超时时间的那一时刻起 new_value.it_value 指定的时间间隔后,定时器会超时。

需要注意,可以多次调用 timerfd_settime,若上次调用 timerfd_settime 启动的定时器还为超时,则这次调用的 timerfd_settime 会覆盖掉上次还没超时的定时器,即上次设置的定时器会失效。

下面看两个例子:

示例一:设置一个定时器,死等定时器超时(通过read进行阻塞读)

#include <iostream>
#include <sys/timerfd.h>
#include <cstring>
#include <unistd.h>

int main()
{
	struct timespec curr_time;
    bzero(&curr_time, sizeof(curr_time));
    // timerfd 为阻塞的
    int timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
    
    // 获取调用 timerfd_create 时的时间
    clock_gettime(CLOCK_MONOTONIC, &curr_time);
    std::cout << "timerfd_create time: " << "sec-" << curr_time.tv_sec 
			  << " nano-" << curr_time.tv_nsec << std::endl;

    sleep(3);

    struct itimerspec new_val;
    bzero(&new_val, sizeof(new_val));
    new_val.it_value.tv_sec = 5;    // 5s后定时器超时
    timerfd_settime(timerfd, 0, &new_val, nullptr);

    // 获取调用 timerfd_settime 时的时间
    bzero(&curr_time, sizeof(curr_time));
    clock_gettime(CLOCK_MONOTONIC, &curr_time);
    std::cout << "timerfd_settime time: " << "sec-" << curr_time.tv_sec 
			  << " nano-" << curr_time.tv_nsec << std::endl;

    uint64_t howmany;
    // 阻塞读
    ssize_t n = read(timerfd, &howmany, sizeof(howmany));

    // 获取定时器超时时刻
    bzero(&curr_time, sizeof(curr_time));
    clock_gettime(CLOCK_MONOTONIC, &curr_time);
    std::cout << "timerfd timeout time: " << "sec-" << curr_time.tv_sec 
			  << " nano-" << curr_time.tv_nsec << std::endl;
}

/*
运行结果:
timerfd_create time: sec-101418 nano-833894291
timerfd_settime time: sec-101421 nano-834455725
timerfd timeout time: sec-101426 nano-834540240
*/

示例二:设置一个周期定时器

// 省略相关头文件

int main()
{
    struct itimerspec expire_time;
    bzero(&expire_time, sizeof(expire_time));
    int timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
    
    int epollfd = epoll_create1(EPOLL_CLOEXEC);
    struct epoll_event events[5];
    bzero(events, sizeof(events));
    
    struct epoll_event timer_event;
    bzero(&timer_event, sizeof(timer_event));
    timer_event.events = EPOLLIN;
    timer_event.data.fd = timerfd;

    expire_time.it_value.tv_sec = 5;
    // 5s 的定时周期
    expire_time.it_interval.tv_sec = 5;
    if (timerfd_settime(timerfd, 0, &expire_time, nullptr) < 0) {
        error_message("timerfd_create error");
    }
   
    epoll_ctl(epollfd, EPOLL_CTL_ADD, timerfd, &timer_event);

    struct timespec curr_time;
    

    while (true) {
        int num = epoll_wait(epollfd, events, 5, -1);

        bzero(&curr_time, sizeof(curr_time));
        clock_gettime(CLOCK_MONOTONIC, &curr_time);
        std::cout << "curr time: sec-" << curr_time.tv_sec << ", nano-" << curr_time.tv_nsec << std::endl;

        for (int i = 0; i < num; ++i) {
            int fd = events[i].data.fd;
            if (fd == timerfd) {
                std::cout << "timer timeout once." << std::endl;

                uint64_t howmany;
                ssize_t n = read(timerfd, &howmany, sizeof(howmany));
                std::cout << "howmany: " << howmany << std::endl;
            }
        }
    }
}

/*
运行输出:
curr time: sec-103737, nano-656002232
timer timeout once.
howmany: 1
curr time: sec-103742, nano-655951279
timer timeout once.
howmany: 1
curr time: sec-103747, nano-655957241
timer timeout once.
howmany: 1
curr time: sec-103752, nano-655909670
timer timeout once.
howmany: 1
...
*/

若要使用绝对时间设置超时时间,可以设置第二个参数 flags

flags 参数可以由下列值进行或运算设置,若使用默认行为,则设置为0。

  • TFD_TIMER_ABSTIME 若被设置,则当 new_value.it_value 所指定的时刻到达时,定时器超时。
  • TFD_TIMER_CANCEL_ON_SET 若被设置。If this flag is specified along with TFD_TIMER_ABSTIME and the clock for this timer is CLOCK_REALTIME or CLOCK_REALTIME_ALARM, then mark this timer as cancelable if the real-time clock undergoes a discontinuous change (settimeofday(2), clock_settime(2), or similar). When such changes occur, a current or future read(2) from the file descriptor will fail with the error ECANCELED.

下面给出一个使用绝对时间设置定时器超时的例子:

int main()
{
	int timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
    struct itimerspec new_val;
    struct timespec curr_time;
    bzero(&new_val, sizeof(new_val));
    bzero(&curr_time, sizeof(curr_time));

    clock_gettime(CLOCK_MONOTONIC, &curr_time);
    std::cout << "curr_time: " << curr_time.tv_sec 
			  << " seconds, " << curr_time.tv_nsec << "nanoseconds" << std::endl;

    // 使用绝对时间设置定时器的超时时间
    new_val.it_value.tv_sec = curr_time.tv_sec + 5;
    timerfd_settime(timerfd, TFD_TIMER_ABSTIME, &new_val, nullptr);

    uint64_t howmany;
    ssize_t n = read(timerfd, &howmany, sizeof(howmany));

    bzero(&curr_time, sizeof(curr_time));
    clock_gettime(CLOCK_MONOTONIC, &curr_time);
    std::cout << "curr_time: " << curr_time.tv_sec 
			  << " seconds, " << curr_time.tv_nsec << "nanoseconds" << std::endl;

    close(timerfd);
}

timerfd_gettime

timerfd_gettime 用于获取 fd 所指定的定时器的时间信息,具体地,将定时器的到期时刻距离调用timerfd_gettime 时刻之间的时间间隔存储到 curr_value.it_value 中,而 curr_value.it_interval 保存了下一个定时事件的定时周期。下面看一个例子:

int main()
{
	int timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
    struct itimerspec new_val, old_val;
    bzero(&new_val, sizeof(new_val));
    bzero(&old_val, sizeof(old_val));

    clock_gettime(CLOCK_MONOTONIC, &old_val.it_value);
    // 调用 timerfd_settiem 时的时间
    std::cout << "sec: " << old_val.it_value.tv_sec << ", nano: " << old_val.it_value.tv_nsec << std::endl;
    bzero(&old_val, sizeof(old_val));

    new_val.it_value.tv_sec = 10;
    timerfd_settime(timerfd, 0, &new_val, &old_val);
    // 因为 timerfd_settime 在此处是第一次调用,因此 old_val 的值为0
    std::cout << "sec: " << old_val.it_value.tv_sec << ", nano: " << old_val.it_value.tv_nsec << std::endl;

    sleep(3);

    bzero(&old_val, sizeof(old_val));
    clock_gettime(CLOCK_MONOTONIC, &old_val.it_value);
    // sleep 3s 后,获取当前时刻的时间
    std::cout << "sec: " << old_val.it_value.tv_sec << ", nano: " << old_val.it_value.tv_nsec << std::endl;


    bzero(&old_val, sizeof(old_val));
    timerfd_settime(timerfd, 0, &new_val, &old_val);
    // 第二次调用 timerfd_settime,
    // 因为上一次调用 timerfd_settime 设置的定时器还未超时,因此上次的定时器失效
    // 距离上次设置的定时器超时还剩的时间间隔会被设置到 old_val 中
    std::cout << "sec: " << old_val.it_value.tv_sec << ", nano: " << old_val.it_value.tv_nsec << std::endl;

    uint64_t howmany;
    // 阻塞读
    ssize_t n = read(timerfd, &howmany, sizeof(howmany));

    // 定时器超时时刻
    bzero(&old_val, sizeof(old_val));
    clock_gettime(CLOCK_MONOTONIC, &old_val.it_value);
    std::cout << "sec: " << old_val.it_value.tv_sec << ", nano: " << old_val.it_value.tv_nsec << std::endl;
}

// 观察输出结果:
/*
sec: 105111, nano: 956075955
sec: 0, nano: 0
sec: 105114, nano: 956573191
sec: 6, nano: 999455855
sec: 105124, nano: 956734201
*/