timerfd 初级笔记1

作者: | 更新日期:

几个月前我使用timerfd封装了一个定时器,使用了这么久一切正常。今天回顾一下timerfd的基础知识。

本文首发于公众号:天空的代码世界,微信号:tiankonguse

timerfd是什么

timerfd 是 Linux 为用户程序提供的一个定时器接口。

这个接口基于文件描述符,然后我们就可以和 epoll 组合做很多事了。

关于 epoll 的记录可以看看这里 epoll初级笔记1.

创建定时器

使用 timerfd_create 函数可以创建一个定时器。

#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);

参数 clockid 可以是 CLOCK_MONOTONIC 或者 CLOCK_REALTIME 。
这两个参数的区别如下:

  • CLOCK_REALTIME 参考系统的实时时间,如果系统时间被改变,定时器的参考时间也改变
  • CLOCK_MONOTONIC 参考建定时器时的时间,如果之后系统时间被修改, 定时器里的参考时间不变。

flags 参数在 linux 2.6.26 版本之后,必须为0。

之前参数可以使用 TFD_NONBLOCK 和 TFD_CLOEXEC 通过异或配置。

函数执行成功返回一个文件描述符,失败的话返回-1,错误码需要查errno。

错误码如下

  • EINVAL 不能识别clockid。
  • EINVAL flags 无效; 或者,在 2.6.26 及其前,flags 非零。
  • EMFILE 达到单个进程打开的文件描述上限。
  • ENFILE 达到可打开文件个数的系统全局上限。
  • ENODEV 不能挂载(内部)匿名结点设备。
  • ENOMEM 内存不足

设置非阻塞模式

由于timerfd_create的flags参数现在必须为0了, 所以我们必须手动设置这个FD为非阻塞模式了。

设置代码如下, 获取现有FD的参数, 然后加上 O_NONBLOCK, 最后再设置我们的FD即可。

flags = fcntl(fd, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags)

我这里有简单的介绍 fcntl 函数 - 简单了解linux 文件I/O.

不懂 fcntl 的人可以来这里快速了解一下这个函数的功能。

设置超时时间

创建完定时器的FD了,肯定要设置超时时间才能启动定时作用啦。

#include <sys/timerfd.h>

int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);
  • fd 就是我们的定时器文件描述符。
  • flags 1代表设置的是绝对时间;为0代表相对时间。
    绝对时间的意思是到那个时间点触发事件
    相对时间是多少时间之后触发事件
  • new_value 需要设置的时间
  • old_value 需要返回的上次设置的时间,一般传NULL指针。

得到设置的时间

有时候,我们需要手动定时器时,可能需要查询上次设置的时间,这时可以使用 timerfd_gettime 函数。

#include <sys/timerfd.h>

int timerfd_gettime(int fd, struct itimerspec *curr_value);

定时器的数据结构

我们在设置时间和得到时间时,遇到了结构体 struct itimerspec, 它的结构如下。

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

struct itimerspec {
    struct timespec it_interval;  /* Interval for periodic timer */
    struct timespec it_value;     /* Initial expiration */
};

简单使用样例

//创建定时器
timer_id = timerfd_create(CLOCK_REALTIME, 0);

//设置非阻塞
flags = fcntl(fd, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags)

//设置超时时间
double timer_internal = 3.2;
struct itimerspec ptime_internal;
memset(&ptime_internal, 0, sizeof(ptime_internal));
ptime_internal.it_value.tv_sec = (int) timer_internal;
ptime_internal.it_value.tv_nsec = (timer_internal - (int) timer_internal) * 1000000;

timerfd_settime(timer_id, 0, &ptime_internal, NULL);

//其他操作

本文首发于公众号:天空的代码世界,微信号:tiankonguse
如果你想留言,可以在微信里面关注公众号进行留言。

关注公众号,接收最新消息

tiankonguse +
穿越