timefd介绍

使用 timerfd 创建出的定时器是基于文件描述符进行管理的,在达到超时时间时,描述符将置为可读,并可以从中读取到超时次数(启动定时器后或上次 read 之后的超时次数)。

1
2
3
4
5
6
7
#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);

接口

timerfd_create--创建一个定时器文件描述符

1
2
3
返回值是一个新的文件描述符,失败时返回 -1

int timerfd_create(int clockid, int flags);

clockid: 定时器使用的时钟

  • CLOCK_REALTIME:系统的实时时钟,可被系统设置改变。
  • CLOCK_MONOTONIC:单调递增的时钟,不会受系统时间调整的影响。

flags: 控制选项

  • TFD_NONBLOCK:非阻塞模式。
  • TFD_CLOEXEC:文件描述符在执行 exec 时关闭。

timerfd_settime--配置定时器

1
2
3
int timerfd_settime(int fd, int flags,
const struct itimerspec *new_value,
struct itimerspec *old_value);

重点看 struct itimerspec 结构体,配置定时器的核心参数:

1
2
3
4
5
6
7
8
9
struct itimerspec {
struct timespec it_interval; // 设置定时器的执行周期,也就是定时器每隔多久会触发一次
struct timespec it_value; // 设置定时器初始的溢出时间,也就是第一次设置后多久会触发一次
};

struct timespec {
time_t tv_sec; // 秒
long tv_nsec; // 纳秒
};

然后再对其他三个参数进行说明:

fd:前面 timerfd_create 创建成功的返回值。

old_value:可以为 NULL;如果不是 NULL 将返回上次定时器设置的 new_value 参数。

flags:0 代表相对事件;TFD_TIMER_ABSTIME 代表绝对时间。

timerfd_gettime--知晓定时器的当前状态

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

获取定时器的当前剩余时间(it_value)以及定时器的周期时间(it_interval)。

如何把 timerfd 用起来?

前面讲 timerfd 在达到超时时间时,描述符将置为可读。那我们调用 read 接口即可。

1
ssize_t read(int fd, void *buf, size_t count);

阻塞和非阻塞的处理方式

默认情况下,time_fd 是阻塞模式。如果要 设置为非阻塞模式,可以通过 timerfd_create 设置 flags 选项为 TFD_NONBLOCK。

如果是阻塞模式下调用 read,read 调用会阻塞线程,直到定时器事件发生(不发生就一直阻塞)。

如果是非阻塞模式下调用 read,read 会立即返回,如果没有事件触发,返回 -1,并设置 errnoEAGAIN

正因为 阻塞模式下必然是在 定时器有事件发生才解除阻塞,也就保证代码执行到 read 之后必然有事件发生:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
while ( 1 )
{
int nfds = epoll_wait( epfd, events, 10, -1 );
for ( int i = 0; i < nfds; i++ )
{
if ( events[i].data.fd == tfd )
{
uint64_t expirations;
read( tfd, &expirations, sizeof(expirations) );
printf( "Timer expired %llu times\n", (unsigned long long) expirations );
}
}
}

而非阻塞模式并不能保证代码往下执行是因为确保有事件发生,我们需要有判断逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
while ( 1 )
{
ssize_t s = read( tfd, &expirations, sizeof(expirations) );
if ( s == -1 )
{
if ( errno == EAGAIN ) // 没有定时器事件发生
{
printf( "No timer event yet, doing other work...\n" );
continue;
} else {
perror( "read" );
return(-1);
}
}
printf( "Timer expired %llu times\n", (unsigned long long) expirations );
break;
}