Linux定时器timerfd用法
timerfd特点
timerfd的特点是将时间变成一个文件描述符,定时器超时时,文件可读。这样就能很容易融入select(2)/poll(2)/epoll(7)的框架中,用统一的方式来处理IO事件、超时事件。这也是Reactor模式的特点。
timerfd定时器与传统Reactor模式定时器
传统Reactor模式使用select/poll/epoll 的timeout参数实现定时功能,但其精度只有毫秒(注意区分表示精度和实际精度,表示精度可能为微妙和纳秒)。
另外,select/poll/epoll的定时器也有一个缺陷,那就是只能针对的是所有监听的文件描述符fd,而非绑定某个fd。
timerfd可以解决这个问题,单独为某个fd指定定时器。
timerfd接口
timerfd包含3个接口:timerfd_create,timerfd_settime,timerfd_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);
1)timerfd_create 用于创建定时器对象,返回一个指向该定时器的fd。
参数
clockid 用于创建定时器的过程,只能是CLOCK_REALTIME或CLOCK_MONOTONIC。CLOCK_REALTIME表示创建一个可设置的系统范围的时钟(system-wide clock)。CLOCK_MONOTONIC表示创建一个不可设置的时钟,不受系统时钟中的非连续改变的影响(例如,手动改变系统时间)
flags 选项,值能按位或,可用于改变timerfd_create()行为。
- TFD_NONBLOCK 为新打开的fd设置O_NONBLOCK选项,可以节约额外调用fcntl实现通用结果。
- TFD_CLOEXEC 为新打开的fd设置close-on-exec(FD_CLOEXEC)选项,在fork + exec后,新进程自动关闭该fd。同样的,可以节约额外调用open指定O_CLOEXEC选项。
返回值
成功,返回一个新的文件描述符(绑定到一个内部定时器);错误,返回-1并且errno被设置。
2)timerfd_settime 用于启动或停止绑定到fd的定时器。
参数
fd 由timerfd_create的定时器对应文件描述符
flags 选项
- 0,启动一个相对定时器,基于当前时间 + new_value.it_value指定的相对定时值。
- TFD_TIMER_ABSTIME,启动一个绝对定时器(由new_value.it_value指定定时值)。
new_value 定时器新的定时值,根据flags不同有不同含义。
old_value 定时器旧的定时值。可以为NULL,如果非NULL,说明之前设置过。
返回值
成功,返回0;错误,返回-1,且errno被设置。
3)timerfd_gettime 用于获取fd对应定时器的当前时间值
参数
fd 由timerfd_create的定时器对应文件描述符。
curr_value 保存定时器的当前定时值。
返回值
成功,返回0;错误,返回-1,且errno被设置。
4)操作定时器fd
- read(2) 定时器超时时,fd可读,read读取fd返回1个无符号8byte整型(uint64_t,主机字节序,存放当buf中),表示超时的次数。如果没有超时,read将会阻塞到下一次定时器超时,或者失败(errno设置为EAGAIN,fd设置为非阻塞)。另外,如果提供的buffer大小 < 8byte,read将返回EINVAL。read成功时,返回值应为8。
- poll(2)/select(2)/epoll (7) 监听定时器fd,fd可读时,会收到就绪通知。
- close(2) 关闭fd对应定时器。如果不需要该定时器,可调用close关闭之。
timerfd使用示例
timerfd + poll + read
#include <iostream>
#include <sys/timerfd.h>
#include <poll.h>
#include <unistd.h>
#include <assert.h>
using namespace std;
int main() {
struct itimerspec timebuf;
int timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
timebuf.it_interval = {1, 0}; // period timeout value = 1s
timebuf.it_value = {5, 100}; // initial timeout value = 5s100ns
timerfd_settime(timerfd, 0, &timebuf, NULL);
struct pollfd fds[1];
int len = sizeof(fds) / sizeof(fds[0]);
fds[0].fd = timerfd;
fds[0].events = POLLIN | POLLERR | POLLHUP;
while (true)
{
int n = poll(fds, len, -1);
for (int i = 0; i < len && n-- > 0; ++i) {
if (fds[i].revents & POLLIN)
{
uint64_t val;
int ret = read(timerfd, &val, sizeof(val));
if (ret != sizeof(val)) // ret should be 8
{
cerr << "read " << ret << "bytes instead of 8 frome timerfd" << endl;
break;
}
cout << "timerfd = " << timerfd << " timeout!" << endl;
}
}
}
close(timerfd);
return 0;
}