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;
}
posted @ 2022-02-12 13:39  明明1109  阅读(3961)  评论(0编辑  收藏  举报