如何正确使用timerfd?

How to use timerfd properly?

我使用 timerfd 和 zmq。

如何使用 timerfd_createtimerfd_set 等待计时器 (https://man7.org/linux/man-pages/man2/timerfd_create.2.html) 一秒钟?

我已经查看了 link,但我仍然不明白如何初始化一个计时器,该计时器在创建和设置时每次滴答等待一秒。这正是我的任务:

We start a timer with timerfd_create(), which is 1 / sec. ticking. When setting a timer with timer_set_(..) a counter is simply incremented, which is decremented with every tick. When the counter reaches 0, the timer has expired.

在这个项目中,我们有一个函数计时器 _ set _(),其中计时器是通过函数 timerfd_create 和 timerfd_settimer() 设置的。我希望你能帮助我。

这是我的进度(部分代码):

    struct itimerspec timerValue;

    g_items[n].socket = nullptr; 
    g_items[n].events = ZMQ_POLLIN;

    g_items[n].fd = timerfd_create(CLOCK_REALTIME, 0);
    if(g_items[n].fd == -1 ){
        printf("timerfd_create() failed: errno=%d\n", errno);
        return -1;
    }  

    timerValue.it_value.tv_sec = 1;
    timerValue.it_value.tv_nsec = 0;
    timerValue.it_interval.tv_sec = 1;
    timerValue.it_interval.tv_nsec = 0;

    timerfd_settime(g_items[n].fd,  0, &timerValue, NULL); 

对于 timerfds,想法是 fd 上的 read 将 return 计时器过期的次数。

来自 timerfd_settime(2) 手册页:

Operating on a timer file descriptor The file descriptor returned by timerfd_create() supports the following operations:

read(2) If the timer has already expired one or more times since its settings were last modified using timerfd_settime(), or since the last successful read(2), then the buffer given to read(2) returns an unsigned 8-byte integer (uint64_t) containing the number of expirations that have occurred.

If no timer expirations have occurred at the time of the read(2), then the call either blocks until the next timer expiration, or fails with the error EAGAIN if the file descriptor has been made nonblocking (via the use of the fcntl(2) F_SETFL operation to set the O_NONBLOCK flag).

所以,基本上,您创建一个无符号的 8 字节整数(uint64_t on Linux),并将其传递给您的读取调用。

uint64_t buf;
int expired = read( g_items[n].fd, &buf, sizeof(uint64_t));
if( expired < 0 ) perror("read");

类似的东西,如果你想阻塞直到到期。

出现关于正确设置计时器超时的问题。

随着设置

timerValue.it_value.tv_sec = 1;
timerValue.it_value.tv_nsec = 0;
timerValue.it_interval.tv_sec = 1;
timerValue.it_interval.tv_nsec = 0;

您正确地将 初始超时 设置为 1 秒(字段 timerValue.it_value)。但是你还设置了1s的periodic interval,而且你没有提到这样做的意愿。


关于超时

此行为在手册的以下段落中有所描述:

int timerfd_create(int clockid, int flags);

new_value.it_value specifies the initial expiration of the timer, in seconds and nanoseconds. Setting either field of new_value.it_value to a nonzero value arms the timer.
Setting both fields of new_value.it_value to zero disarms the timer.

Setting one or both fields of new_value.it_interval to nonzero values specifies the period, in seconds and nanoseconds, for repeated timer expirations after the initial expiration. If both fields of new_value.it_interval are zero, the timer expires just once, at the time specified by new_value.it_value.

最后一段的重点是我的,因为它显示了如何做才能拥有一个 single-shot 计时器。


timerrfd 的好处。如何检测定时器到期?

timerfd 提供的主要优点是计时器与文件描述符相关联,这意味着它

may be monitored by select(2), poll(2), and epoll(7).

另一个答案中包含的关于 read() 的信息也是有效的:我们只是说,即使使用 select()read() 等功能,为了使用文件描述符中的数据。


一个完整的例子

在下面的演示程序中,设置了4秒的超时时间;之后设置 5 秒的周期性间隔。

好老的select()是用来等待定时器超时的,read()是用来消费数据的(也就是过期的超时次数,我们忽略)。

#include <stdio.h>
#include <sys/timerfd.h>
#include <sys/select.h>
#include <time.h>

int main()
{
    int tfd = timerfd_create(CLOCK_REALTIME,  0);
    
    printf("Starting at (%d)...\n", (int)time(NULL));
    
    if(tfd > 0)
    {
       char dummybuf[8];
        struct itimerspec spec =
        {
            { 5, 0 }, // Set to {0, 0} if you need a one-shot timer
            { 4, 0 }
        };
        timerfd_settime(tfd, 0, &spec, NULL);

        /* Wait */
        fd_set rfds;
        int retval;

        /* Watch timefd file descriptor */
        FD_ZERO(&rfds);
        FD_SET(0, &rfds);
        FD_SET(tfd, &rfds);

        /* Let's wait for initial timer expiration */
        retval = select(tfd+1, &rfds, NULL, NULL, NULL); /* Last parameter = NULL --> wait forever */
        printf("Expired at %d! (%d) (%d)\n", (int)time(NULL), retval, read(tfd, dummybuf, 8) );
        
        /* Let's wait for initial timer expiration */
        retval = select(tfd+1, &rfds, NULL, NULL, NULL);
        printf("Expired at %d! (%d) (%d)\n", (int)time(NULL), retval, read(tfd, dummybuf, 8) );

        retval = select(tfd+1, &rfds, NULL, NULL, NULL);
        printf("Expired at %d! (%d) (%d)\n", (int)time(NULL), retval, read(tfd, dummybuf, 8) );
    }
    
    return 0;
}

这是输出。每行还包含时间戳,以便可以检查实际经过的时间>

Starting at (1596547762)...
Expired at 1596547766! (1) (8)
Expired at 1596547771! (1) (8)
Expired at 1596547776! (1) (8)

请注意:

  • 我们刚刚进行了 3 次读取,用于测试
  • 间隔为4s + 5s + 5s(初始超时+两次间隔超时)
  • read() 返回 8 个字节。我们忽略了它们,但它们包含过期超时的数量