特点
Linux 内核于内核2.6.25提供了一种创建定时器的 API, 那就是 Linux 特有的 timerfd API, 并且该定时器可以从文件描述符中读取其创建的定时器到期通知.
这也意味着, 可以将文件描述符通过多路复用监听并读取, 而其他的定时器想要将一个 (或多个) 定时器与一组文件描述符放在一起同时监听可不是一件容易的事.
函数接口
timerfd 系列 API 有三个系统调用 :
- 第一个系统调用, 该系统调用创建一个新的定时器对象 :
#include <sys/timerfd.h>
int timerfd_create(int clockid, int flags);
//成功返回一个指代该对象的文件描述符, 失败返回-1及errno
第一个参数 clockid 可以设置为 CLOCK_REALTIME
和 CLOCK_MONOTONIC
CLOCK_REALTIME
可设定的系统级实时时钟.
相对时间,从1970.1.1到目前的时间。更改系统时间会更改获取的值。也就是说,它以系统时间为坐标。
CLOCK_REALTIME
用于度量真实时间, 他的设置可以变更
CLOCK_MONOTONIC
不可设定的恒定态时钟
与CLOCK_REALTIME相反,它是以绝对时间为准,获取的时间为系统重启到现在的时间,更改系统时间对齐没有影响。
CLOCK_MONOTONIC
时钟适用于那些无法容忍系统时钟发生跳跃性变化(如, 手动改变了系统时间)的应用上
第二个参数flags, 支持 TFD_CLOEXEC
和 TFD_NONBLOCK
和 0
TFD_CLOEXEC
为新的文件描述符设置运行时关闭标志 (FD_CLOEXEC)
TFD_NONBLOCK
为底层的打开文件描述符设置 O_NONBLOCK 标志, 随后的读操作将是非阻塞的, 这与调用 fcntl 效果相同
使用完定时器要记得 close
- 第二个系统调用, 可以启动或停止由文件描述符 fd 所指代的定时器
#include <sys/timerfd.h>
int timerfd_settime(int fd, int flags, const struct
itimerspec* new_value, struct itimerspec* old_value);
//成功返回0, 失败返回-1和 errno
flags
的值可以是 0 (相对时间), 可以是 TFD_TIMER_ABSTIME
(绝对时间)
而第二第三个参数都是struct itimerspec*
类型
struct itimerspec
{
struct timespec it_interval; //间隔时间
struct timespec it_value; //第一次到期时间
};
struct timespec
{
time_t tv_sec; //秒
long tv_nsec; //纳秒
};
new_value
为定时器指定新设置, old_value
用来返回定时器的前一设置, 如果不关心, 可将其设置为 NULL
new_value
: 指定新的超时时间,若 newValue.it_value
非 0 则启动定时器,否则关闭定时器。若 newValue.it_interval
为 0 则定时器只定时一次,否则之后每隔设定时间超时一次。
old_value
:不为 NULL 时则返回定时器这次设置之前的超时时间。
timerfd_create第一个参数和 clock_gettime(获取时间的函数,下面介绍) 的第一个参数都是 CLOCK_REALTIME 或者 CLOCK_MONOTONIC,timerfd_settime 的第二个参数为0(相对时间)或者 TFD_TIMER_ABSTIME(绝对时间)
- 如果 timerfd_settime 设置为 TFD_TIMER_ABSTIME(绝对时间),则后面的时间必须用 clock_gettime 来获取,获取时设置 CLOCK_REALTIME 还是 CLOCK_MONOTONIC 取决于 timerfd_create 设置的值。
- 如果 timerfd_settime 设置为 0(相对定时器),则后面的时间必须用相对时间
- 第三个系统调用, 返回文件描述符 fd 所标识定时器的间隔及剩余时间
#include <sys/timerfd.h>
int timerfd_gettime(int fd, struct itimerspec *curr_value);
//成功返回0, 失败返回-1和errno
间隔和距离下次到期的时间均返回到 curr_value 指向的结构体.
如果返回的结构中 curr_value.it_value
中所有字段值均为0, 那么该定时器已经解除, 如果返回的结构 curr_value.it_interval
中两字段值均为0, 那么该定时器只会到期一次, 到期时间在 curr_value.it_value
中给出
- 获取时钟的值
#include <time.h>
int clock_gettime(clockid_t clk_id, struct timespect *tp);
使用 epoll 监听定时器到期事件
#include <sys/timerfd.h>
#include <iostream>
#include <time.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <assert.h>
using namespace std;
const int MAXNUM = 20;
int main(int argc, char *argv[])
{
struct itimerspec new_value;
struct timespec now;
uint64_t exp;
ssize_t s;
int ret = clock_gettime(CLOCK_REALTIME, &now);//获取时钟时间
assert(ret != -1);
new_value.it_value.tv_sec = 5; //第一次到期的时间
new_value.it_value.tv_nsec = now.tv_nsec;
new_value.it_interval.tv_sec = 1; //之后每次到期的时间间隔
new_value.it_interval.tv_nsec = 0;
int timefd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK); // 构建了一个定时器
assert(timefd != -1);
ret = timerfd_settime(timefd, 0, &new_value, NULL);//启动定时器
assert(ret != -1);
cout << "timer started" << endl; // 定时器开启啦!
int epollfd = epoll_create(1); //创建epoll实例对象
struct epoll_event ev;
struct epoll_event events[MAXNUM];
ev.data.fd = timefd;
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_ADD, timefd, &ev); //添加到epoll事件集合
for (; ;)
{
int num = epoll_wait(epollfd, events, MAXNUM, 0);
assert(num >= 0);
for (int i = 0; i < num; i++)
{
if (events[i].events & EPOLLIN)
{
//....处理其他事件
if (events[i].data.fd == timefd)
{
s = read(events[i].data.fd, &exp, sizeof(uint64_t)); //需要读出uint64_t大小, 不然会发生错误
assert(s == sizeof(uint64_t));
cout << "here is timer" << endl;
}
}
}
}
close(timefd);
close(epollfd);
return 0;
}
注意
timerfd_settime
第二个参数如果是 0, 是相对时间, 如果是 TFD_TIMER_ABSTIME
, 代表绝对时间, 但是这里还有需要注意的点
By default, the initial expiration time specified in new_value is interpreted relative to the current time on the timer’s clock at the time of the call (i.e., new_value.it_value specifies a time relative to the current value of the clock specified by clockid).
默认情况下,
new_value
中指定的初始过期时间相对于调用时计时器时钟上的当前时间(即,new_value.it_value
指定相对于 clockid 指定的时钟的当前值的时间)。
也就是说
new_value.it_value
设置初次到期的时间firsttime
如果timerfd_settime
第二个参数设置为 0, new_value.it_value = firsttime
如果timerfd_settime
第二个参数设置为TFD_TIMER_ABSTIME
,new_value.it_value = now.tv_sec + firsttime