TIMERFD_CREATE - Linux手册页

时间:2019-08-20 17:59:35  来源:igfitidea点击:

Linux程序员手册 第2部分
更新日期: 2020-08-13

名称

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

说明

这些系统调用创建并在计时器上运行,该计时器通过文件描述符传递计时器到期通知。它们提供了使用setitimer(2)或timer_create(2)的替代方法,其优点是可以通过select(2),poll(2)和epoll(7)监视文件描述符。

这三个系统调用的使用类似于使用timer_create(2),timer_settime(2)和timer_gettime(2)。 (没有timer_getoverrun(2)的类似物,因为该功能由read(2)提供,如下所述。)

timerfd_create()

timerfd_create()创建一个新的计时器对象,并返回引用该计时器的文件描述符。 clockid参数指定用于标记计时器进度的时钟,并且必须为以下之一:

CLOCK_REALTIME
可设置的系统范围的实时时钟。
CLOCK_MONOTONIC
不可设置的单调递增时钟,用于测量从过去某个未指定点开始的时间,该时间在系统启动后不会更改。
CLOCK_BOOTTIME(Since Linux 3.15)
像CLOCK_MONOTONIC一样,这是一个单调递增的时钟。但是,尽管CLOCK_MONOTONIC时钟不测量系统挂起时的时间,但CLOCK_BOOTTIME时钟确实包括系统挂起的时间。这对于需要暂停感知的应用程序很有用。 CLOCK_REALTIME不适合此类应用,因为该时钟受系统时钟不连续更改的影响。
CLOCK_REALTIME_ALARM(since Linux 3.11)
该时钟类似于CLOCK_REALTIME,但是如果挂起它将唤醒系统。调用者必须具有CAP_WAKE_ALARM功能才能针对此时钟设置计时器。
CLOCK_BOOTTIME_ALARM(since Linux 3.11)
该时钟类似于CLOCK_BOOTTIME,但是如果挂起了系统,它将唤醒系统。调用者必须具有CAP_WAKE_ALARM功能才能针对此时钟设置计时器。

有关上述时钟的更多详细信息,请参见clock_getres(2)。

可以使用clock_gettime(2)检索每个时钟的当前值。

从Linux 2.6.27开始,可以对标志中的以下值进行按位或运算,以更改timerfd_create()的行为:

TFD_NONBLOCK
在新文件描述符引用的打开文件描述(请参见open(2))上设置O_NONBLOCK文件状态标志。使用此标志可以节省对fcntl(2)的额外调用,以实现相同的结果。
TFD_CLOEXEC
在新文件描述符上设置执行时关闭(FD_CLOEXEC)标志。有关为什么可能有用的原因,请参见open(2)中O_CLOEXEC标志的描述。

在2.6.26(含)以下的Linux版本中,标志必须指定为零。

timerfd_settime()

timerfd_settime()武装(启动)或撤消(停止)文件描述符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中指定的初始到期时间将相对于调用时计时器时钟上的当前时间进行解释(即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)或类似的时钟),则将此计时器标记为可取消。发生此类更改时,当前或将来从文件描述符读取(2)将会失败,并显示错误ECANCELED。

如果old_value参数不为NULL,则它指向的itimerspec结构用于返回调用时当前计时器的设置;请参阅以下有关timerfd_gettime()的描述。

timerfd_gettime()

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(2)起,定时器已经过期一次或多次,则提供给read(2)的缓冲区将返回一个无符号的8字节整数(uint64_t)包含已发生的过期次数。 (返回的值以主机字节顺序---即主机上整数的本机字节顺序。)
如果在read(2)时没有计时器到期,则调用将阻塞直到下一个计时器到期,或者如果文件描述符已被设置为非阻塞(通过使用fcntl(2),则调用将失败并显示错误EAGAIN )F_SETFL操作来设置O_NONBLOCK标志)。
如果提供的缓冲区的大小小于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,则时钟的不连续负变化(例如,clock_settime(2))可能导致读取(2)取消阻塞,但如果在时间到期之后但在文件描述符的read(2)之前发生时钟更改,则返回0值(即,不读取任何字节)。
poll(2), select(2) (and similar)
如果一个或多个计时器到期,则文件描述符是可读的(select(2)readfds参数; poll(2)POLLIN标志)。
文件描述符还支持其他文件描述符多路复用API:pselect(2),ppoll(2)和epoll(7)。
ioctl(2)
The following timerfd-specific command is supported:
TFD_IOC_SET_TICKS(since Linux 3.17)
调整已发生的计时器到期数。该参数是一个指向非零的8字节整数(uint64_t *)的指针,该整数包含新的到期数。设置好数字后,计时器上的任何侍者都将被唤醒。该命令的唯一目的是为了恢复检查点/还原目的而过期。仅当内核配置了CONFIG_CHECKPOINT_RESTORE选项时,此操作才可用。
close(2)
当不再需要文件描述符时,应将其关闭。与同一个计时器对象关联的所有文件描述符都已关闭后,计时器将撤防,内核将释放其资源。

fork(2) semantics

fork(2)之后,子级继承了timerfd_create()创建的文件描述符的副本。文件描述符引用与父对象中相应文件描述符相同的基础定时器对象,子对象中的read(2)将返回有关定时器到期的信息。

execve(2) semantics

timerfd_create()创建的文件描述符在execve(2)中保留,如果设防了计时器,它将继续生成计时器到期时间。

返回值

成功后,timerfd_create()返回一个新的文件描述符。发生错误时,将返回-1并将errno设置为指示错误。

timerfd_settime()和timerfd_gettime()成功返回0;如果出错,则返回-1,并将errno设置为错误。

错误说明

timerfd_create()可能因以下错误而失败:

EINVAL
时钟无效。
EINVAL
标志无效;或者,在Linux 2.6.26或更早版本中,标志为非零。
EMFILE
已达到打开文件描述符数量的每个进程限制。
ENFILE
已达到系统范围内打开文件总数的限制。
ENODEV
无法安装(内部)匿名inode设备。
ENOMEM
内核内存不足,无法创建计时器。
EPERM
时钟ID为CLOCK_REALTIME_ALARM或CLOCK_BOOTTIME_ALARM,但呼叫者没有CAP_WAKE_ALARM功能。

timerfd_settime()和timerfd_gettime()可能因以下错误而失败:

EBADF
fd不是有效的文件描述符。
EFAULT
new_value,old_value或curr_value无效的指针。
EINVAL
fd不是有效的timerfd文件描述符。

timerfd_settime()也可能因以下错误而失败:

ECANCELED
请参阅注释。
EINVAL
new_value未正确初始化(tv_nsec之一落在0到999,999,999范围之外)。
EINVAL
标志无效。

版本

从内核2.6.25开始,这些系统调用在Linux上可用。从2.8版开始,glibc提供了库支持。

遵循规范

这些系统调用是特定于Linux的。

备注

假设以下情况是使用timerfd_create()创建的CLOCK_REALTIME或CLOCK_REALTIME_ALARM计时器的:

(a)
已使用TFD_TIMER_ABSTIME和TFD_TIMER_CANCEL_ON_SET标志启动了计时器(timerfd_settime());
(b)
随后对CLOCK_REALTIME时钟进行了不连续的更改(例如settimeofday(2));和
(c)
调用方再次调用timerfd_settime()重新设置计时器(首先不对文件描述符进行read(2))。

在这种情况下,将发生以下情况:

*
timerfd_settime()返回-1,并将errno设置为ECANCELED。 (这使呼叫者知道先前的计时器受到时钟的不连续更改的影响。)
*
使用第二个timerfd_settime()调用中提供的设置成功重置了计时器。 (这可能是一次实现事故,但是如果有依赖于此行为的应用程序,则现在不会解决。)

BUGS

当前,与timer_create(2)相比,timerfd_create()支持的时钟ID类型更少。

示例

以下程序创建一个计时器,然后监视其进度。该程序最多接受三个命令行参数。第一个参数指定计时器初始到期的秒数。第二个参数指定计时器的时间间隔(以秒为单位)。第三个参数指定程序在终止之前应允许计时器到期的次数。第二和第三个命令行参数是可选的。

以下shell会话演示了该程序的用法:

$ a.out 3 1 100
0.000: timer started
3.000: read: 1; total=1
4.000: read: 1; total=2
haZ                  # 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
haC                  # type control-C to suspend the program

Program source

#include <sys/timerfd.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>        /* Definition of uint64_t */

#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: %llu; total=%llu\n",
                (unsigned long long) exp,
                (unsigned long long) tot_exp);
    }

    exit(EXIT_SUCCESS);
}

另外参见

eventfd(2),poll(2),read(2),select(2),setitimer(2),signalfd(2),timer_create(2),timer_gettime(2),timer_settime(2),epoll(7),时间(7)

出版信息

这个页面是Linux手册页项目5.08版的一部分。有关项目的说明、有关报告错误的信息以及此页面的最新版本,请访问https://www.kernel.org/doc/man-pages/