timerfd_create
, timerfd_settime
, timerfd_gettime
系列函数将定时器的实现与文件描述符绑定在一起,定时器超时的那一刻文件描述符变得可读,因此可以很好的与 select、poll 和 epoll 结合在一起使用。
timerfd_create
系统调用将创建一个定时器并与一个文件描述符进行关联。
timerfd_settime
启动或停止一个定时器。
timerfd_gettime
用于获取距下一个到期的定时器的时间信息。
cpp
#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_REALTIME
和 CLOCK_MONOTONIC
。
CLOCK_REALTIME
表示的测量真实时间(即挂钟 wall-clock)的全系统时钟。
CLOCK_MONOTONIC
表示定时器使用的时钟是一个单调递增的时钟,不受系统时间的调整影响 。(man clock_gettime)
flags
参数可以为 TFD_NONBLOCK
和 TFD_CLOEXEC
的或运算。
timerfd_settime
fd
参数表示 timerfd_create
创建返回的文件描述符。
new_value
参数用于指定定时器的过期时间,old_value
如果不为空,用于获取调用 timerfd_settime
时的计数器设置,具体见下面 timerfd_gettime
的描述。
new_value
和 old_value
使用结构体 itimerspec
表示,该结构体类型如下所示:
cpp
struct timespec {
time_t tv_sec; /* Seconds */
long tv_nsec; /* Nanoseconds */
};
struct itimerspec {
struct timespec it_interval; /* 定时器的周期间隔 */
struct timespec it_value; /* 初始过期时间 */
};
对于参数 new_value
,new_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进行阻塞读)
cpp
#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
*/
示例二:设置一个周期定时器
cpp
// 省略相关头文件
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.
下面给出一个使用绝对时间设置定时器超时的例子:
cpp
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
保存了下一个定时事件的定时周期。下面看一个例子:
cpp
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
*/