timerfd_create, timerfd_settime, timerfd_gettime - timers that notify via file descriptors
#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);
这些系统调用创建并操作一个计时器,计时器通过文件描述符来通知计时到期,这样就可以通过 select(2)、poll(2) 和 epoll(7) 监视文件描述符从而监听计时器。
这三个系统调用的使用类似于 timer_create(2)、timer_settime(2) 和 timer_gettime(2) 。 (没有与timer_getoverrun(2) 类似的系统调用,因为该功能由 read(2) 提供,如下所述。)
timerfd_create()
int timerfd_create(int clockid, int flags);
timerfd_create() 创建一个新的计时器对象,并返回引用该计时器的文件描述符。 clockid
参数指定使用那种类型的时钟(clock)来实现计时器(timer),并且必须是以下之一:
-
CLOCK_REALTIME
可设置的 system-wide 实时时钟。 -
CLOCK_MONOTONIC
一个不可设置的单调递增时钟,它从过去的某个未指定点测量时间,系统启动后不会改变,且CLOCK_MONOTONIC 时钟不测量系统挂起时的时间。 -
CLOCK_BOOTTIME
与 CLOCK_MONOTONIC 一样,这是一个单调递增的时钟。 不同的是 CLOCK_BOOTTIME 时钟包括系统挂起的时间。 这对于需要挂起感知的应用程序很有用。 CLOCK_REALTIME 不适用于此类应用程序,因为其会受到系统时钟不连续更改的影响(affected by discontinuous changes to the system clock.)。 -
CLOCK_REALTIME_ALARM
这个时钟类似于 CLOCK_REALTIME,但如果它被挂起就会唤醒系统。 调用者必须具有 CAP_WAKE_ALARM capability才能针对此时钟设置计时器。 -
CLOCK_BOOTTIME_ALARM
这个时钟类似于 CLOCK_BOOTTIME,但如果它被挂起就会唤醒系统。 调用者必须具有 CAP_WAKE_ALARM capability才能针对此时钟设置计时器。
有关上述时钟的更多详细信息,请参阅clock_getres(2)。
可以使用clock_gettime(2) 获取每个时钟的当前值。
从 Linux 2.6.27 开始,可以在标志中对以下值进行按位 OR 运算以更改 timerfd_create() 的行为:
- TFD_NONBLOCK
-
TFD_CLOEXEC
凡是返回文件描述符的系统调用,都会拥有以上flag,目前遇到的包括:socket(2)、epoll_create(2)、eventfd(2)、accept(2)等。分别表示非阻塞和执行exec(2)时自动关闭fd。
在 2.6.26 及包括 2.6.26 的 Linux 版本中,标志必须指定为零。
timerfd_settime()
int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);
timerfd_settime() arms (starts) or disarms (stops) the timer referred to by the file descriptor fd.
new_value
参数指定计时器的初始到期时间和到期间隔(换句话说,计时器开始执行后,将会在到达初始到期时间时报告一次,此后每过一个到期间隔就会报告一次)。 用于此参数的 itimerspec 结构包含两个字段,每个字段又是一个 timespec 类型的结构:
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 */
};
new_value.it_value
指定计时器的初始到期时间,以秒和纳秒为单位。 将 new_value.it_value
的任一字段设置为非零值,即可启动计时器。 将 new_value.it_value
的两个字段都设置为零会解除定时器。
将 new_value.it_interval
的一个或两个字段设置为非零值指定初始到期后重复计时器到期的时间段(以秒和纳秒为单位)。 如果 new_value.it_interval
的两个字段都为零,则计时器仅在 new_value.it_value 指定的时间到期一次。
如果将new_value
设置为(10S,2S),即表示,计时器启动后,将会在10S后报告一次,然后每隔2S报告一次;
如果将new_value
设置为(10S,0S),即表示,计时器启动后,将会在10S后报告一次,然后就不再报告了;
如果将new_value
修改为(0S,0S),即表示,停止计时。
默认情况下,new_value
中指定的初始到期时间是相对于调用时计时器时钟上的当前时间的(即,new_value.it_value 是相对于 clockid
指定的时钟的当前值设置的)。 可以通过 flags
参数指定使用绝对时间。
flags 参数是一个位掩码,可以包含以下值:
-
TFD_TIMER_ABSTIME
将 new_value.it_value 解释为计时器时钟上的绝对值。 当计时器的时钟值达到 new_value.it_value 中指定的值时,计时器将到期。 -
TFD_TIMER_CANCEL_ON_SET
如果此标志与 TFD_TIMER_ABSTIME 一起指定,且计时器的时钟为 CLOCK_REALTIME 或 CLOCK_REALTIME_ALARM,那么当时钟发生不连续更改(settimeofday(2)、clock_settime(2) 或类似操作)时,会将此计时器标记为 cancelable,此后对文件描述符执行read(2)操作将失败并显示 ECANCELED 错误。
如果 old_value
参数不为 NULL,则它指向的 itimerspec 结构用于返回调用时当前计时器的设置; 请参阅下面的 timerfd_gettime() 说明。
timerfd_gettime()
int timerfd_gettime(int fd, struct itimerspec *curr_value);
timerfd_gettime() 在 curr_value 中返回一个 itimerspec 结构,该结构包含文件描述符 fd 所引用的计时器的当前设置。
it_value
字段返回计时器下一次到期之前的时间量。 如果此结构的两个字段都为零,则定时器当前已解除。 无论在设置计时器时是否指定了 TFD_TIMER_ABSTIME 标志,该字段始终包含一个相对值。
it_interval
字段返回定时器的间隔。 如果此结构的两个字段都为零,则计时器设置为仅在 curr_value.it_value
指定的时间到期一次。
Operating on a timer file descriptor
timerfd_create() 返回的文件描述符支持以下附加操作:
-
read(2)
如果自上次成功执行timerfd_settime()或read()以来,计时器到期一次或多次,则read(2)操作将返回这期间计时器到期的次数到其指定的8字节大小的无符号整数数据缓冲区中(返回值按主机字节顺序,主机字节序是想对于网络字节序而言的)。如果在 read(2) 时没有发生计时器到期,则调用将阻塞直到下一个计时器到期,或者文件描述符是非阻塞的,将返回EAGAIN错误,这里就给了epoll机制发挥优势的地方。
如果提供的缓冲区的大小小于 8 字节,则 read(2) 失败并显示错误 EINVAL。
如果关联的时钟是 CLOCK_REALTIME 或 CLOCK_REALTIME_ALARM且计时器是绝对的 (TFD_TIMER_ABSTIME),并且在调用 timerfd_settime() 时指定了标志 TFD_TIMER_CANCEL_ON_SET,那么当实时时钟发生不连续变化是,执行read(2)将会失败,并返回ECANCELED错误(这允许应用程序发现时钟的这种不连续变化)。
如果相关时钟是 CLOCK_REALTIME 或 CLOCK_REALTIME_ALARM且定时器是绝对的 (TFD_TIMER_ABSTIME),并且在调用 timerfd_settime() 时没有指定标志 TFD_TIMER_CANCEL_ON_SET。在这种情况下,如果在时间到期之后、 read(2) 操作之前回调时钟(即减小时钟的计时),可能会导致读取 (2) 解除阻塞,但返回值是0(即没有读取字节)。
-
poll(2), select(2) (and similar)
如果一个或多个计时器到期,则文件描述符是可读的(select(2) readfds 参数;poll(2) POLLIN 标志)。文件描述符还支持其他文件描述符复用 API:pselect(2)、ppoll(2) 和 epoll(7)。
-
ioctl(2)
支持以下特定于 timerfd 的命令:-
TFD_IOC_SET_TICKS
调整已发生的计时器到期次数。 参数是一个指向包含新到期次数的非零 8 字节整数 (uint64_t*) 的指针。 一旦设置了数字,定时器上的任何等待的服务都会被唤醒。 此命令的唯一目的是为了在执行 checkpoint/restore 时恢复原来的到期次数。 仅当内核配置了 CONFIG_CHECKPOINT_RESTORE 选项时,此操作才可用。
-
TFD_IOC_SET_TICKS
-
close(2)
当不再需要文件描述符时,应将其关闭。 当与同一计时器对象关联的所有文件描述符都已关闭时,计时器将被解除,内核将释放其资源。
fork(2) semantics
在 fork(2) 之后,子进程继承了 timerfd_create() 创建的文件描述符的副本。 文件描述符引用与父级中相应文件描述符相同的底层计时器对象,子级中的 read(2) 将返回有关计时器到期的信息。
execve(2) semantics
A file descriptor created by timerfd_create() is preserved across execve(2), and continues to generate timer expirations if the timer was armed.
成功时, timerfd_create() 返回一个新的文件描述符。 出错时,返回 -1 并设置 errno 以指示错误。
timerfd_settime() 和 timerfd_gettime() 成功返回 0; 出错时返回 -1,并设置 errno 以指示错误。
timerfd_create() can fail with the following errors:
-
EINVAL
The clockid is not valid. -
EINVAL
flags is invalid; or, in Linux 2.6.26 or earlier, flags is nonzero. -
EMFILE
The per-process limit on the number of open file descriptors has been reached. -
EMFILE
The system-wide limit on the total number of open files has been reached. -
ENODEV
Could not mount (internal) anonymous inode device. -
ENOMEM
There was insufficient kernel memory to create the timer. -
EPERM
clockid
was CLOCK_REALTIME_ALARM or CLOCK_BOOTTIME_ALARM but the caller did not have the CAP_WAKE_ALARM capability.
timerfd_settime() and timerfd_gettime() can fail with the following errors:
-
EBADF
fd is not a valid file descriptor. -
EFAULT
new_value
,old_value
, orcurr_value
is not valid a pointer. -
EINVAL
fd is not a valid timerfd file descriptor.
timerfd_settime() can also fail with the following errors:
-
ECANCELED
See NOTES. -
EINVAL
new_value
未正确初始化(比如tv_nsec
不在 0 - 999,999,999 的范围内)。 -
EINVAL
flags is invalid.
These system calls are available on Linux since kernel 2.6.25.
Library support is provided by glibc since version 2.8.
These system calls are Linux-specific.
假设在使用 timerfd_create() 创建的 CLOCK_REALTIME 或 CLOCK_REALTIME_ALARM 计时器时,发生以下场景:
- 定时器已启动(timerfd_settime()),带有 TFD_TIMER_ABSTIME 和 TFD_TIMER_CANCEL_ON_SET 标志;
- 随后对 CLOCK_REALTIME 时钟进行了不连续的更改(例如 settimeofday(2));
- 调用者再次调用 timerfd_settime() 以重新启动计时器(无需先对文件描述符执行 read(2) 操作)。
在这种情况下,会发生以下情况:
- timerfd_settime() 返回 -1,并将 errno 设置为 ECANCELED(这使调用者能够知道前一个计时器受到时钟不连续更改的影响)。
- 再次调用相同设置的timerfd_settime()将成功重置计时器(这可能是一个实现事故,但现在不会修复,以防有依赖此行为的应用程序)。
目前,timerfd_create() 支持的时钟 ID 类型少于 timer_create(2)。
以下程序创建一个基于实时时钟的绝对时间的计时器,然后监控其进度。 该程序最多接受三个命令行参数。 第一个参数指定计时器初始到期的秒数。 第二个参数指定计时器的间隔,以秒为单位。 第三个参数指定程序在终止前应允许计时器到期的次数。 第二个和第三个命令行参数是可选的。
以下 shell 会话演示了该程序的使用:
$ a.out 3 1 100
0.000: timer started
3.000: read: 1; total=1
4.000: read: 1; total=2
^Z # type control-Z to suspend the program
[1]+ Stopped ./timerfd3_demo 3 1 100
$ fg # Resume execution after a few seconds
a.out 3 1 100
9.660: read: 5; total=7
10.000: read: 1; total=8
11.000: read: 1; total=9
^C # type control-C to suspend the program
源代码
#include <inttypes.h> /* Definition of PRIu64 */
#include <stdint.h> /* Definition of uint64_t */
#include <stdio.h>
#include <stdlib.h>
#include <sys/timerfd.h>
#include <time.h>
#include <unistd.h>
#define handle_error(msg) \
do \
{ \
perror(msg); \
exit(EXIT_FAILURE); \
} while (0)
static void
print_elapsed_time(void)
{
static struct timespec start;
struct timespec curr;
static int first_call = 1;
int secs, nsecs;
if (first_call)
{
first_call = 0;
if (clock_gettime(CLOCK_MONOTONIC, &start) == -1)
handle_error("clock_gettime");
}
if (clock_gettime(CLOCK_MONOTONIC, &curr) == -1)
handle_error("clock_gettime");
secs = curr.tv_sec - start.tv_sec;
nsecs = curr.tv_nsec - start.tv_nsec;
if (nsecs < 0)
{
secs--;
nsecs += 1000000000;
}
printf("%d.%03d: ", secs, (nsecs + 500000) / 1000000);
}
int main(int argc, char* argv[])
{
struct itimerspec new_value;
int max_exp, fd;
struct timespec now;
uint64_t exp, tot_exp;
ssize_t s;
if ((argc != 2) && (argc != 4))
{
fprintf(stderr, "%s init-secs [interval-secs max-exp]\n",
argv[0]);
exit(EXIT_FAILURE);
}
if (clock_gettime(CLOCK_REALTIME, &now) == -1)
handle_error("clock_gettime");
/* Create a CLOCK_REALTIME absolute timer with initial
expiration and interval as specified in command line. */
new_value.it_value.tv_sec = now.tv_sec + atoi(argv[1]);
new_value.it_value.tv_nsec = now.tv_nsec;
if (argc == 2)
{
new_value.it_interval.tv_sec = 0;
max_exp = 1;
}
else
{
new_value.it_interval.tv_sec = atoi(argv[2]);
max_exp = atoi(argv[3]);
}
new_value.it_interval.tv_nsec = 0;
fd = timerfd_create(CLOCK_REALTIME, 0);
if (fd == -1)
handle_error("timerfd_create");
if (timerfd_settime(fd, TFD_TIMER_ABSTIME, &new_value, NULL) == -1)
handle_error("timerfd_settime");
print_elapsed_time();
printf("timer started\n");
for (tot_exp = 0; tot_exp < max_exp;)
{
s = read(fd, &exp, sizeof(uint64_t));
if (s != sizeof(uint64_t))
handle_error("read");
tot_exp += exp;
print_elapsed_time();
printf("read: %" PRIu64 "; total=%" PRIu64 "\n", exp, tot_exp);
}
exit(EXIT_SUCCESS);
}
网友评论