【问题标题】:How to use timerfd properly?如何正确使用 timerfd?
【发布时间】:2020-08-04 21:34:29
【问题描述】:

我在 zmq 中使用timerfd

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

我已经浏览了链接,但我仍然不明白如何初始化一个计时器,该计时器通过创建和设置在每个滴答声中等待一秒。这正是我的任务:

我们用timerfd_create() 启动一个计时器,即1/秒。滴答作响。用 timer_set_(..) 设置定时器时 计数器只是简单地增加,它随着每个滴答而减少。当计数器达到 0 时,定时器 已过期。

在这个项目中,我们有一个函数 timer _ 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); 

【问题讨论】:

    标签: c linux timer zeromq


    【解决方案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;
    

    您正确地将 初始超时 设置为 1 秒(字段 timerValue.it_value)。但是你也设置了一个周期性的间隔 1s,你没有提到这样做的意愿。


    关于超时

    手册的以下段落描述了此行为:

    int timerfd_create(int clockid, int flags);

    new_value.it_value 指定计时器的初始到期时间,以秒和纳秒为单位。将new_value.it_value 的任一字段设置为非零值会启用计时器。
    new_value.it_value 的两个字段设置为零会禁用计时器。

    new_value.it_interval 的一个或两个字段设置为非零值指定初始到期后重复计时器到期的时间段(以秒和纳秒为单位)。 如果new_value.it_interval 的两个字段都为零,则计时器仅在new_value.it_value 指定的时间到期一次。

    最后一段的重点是我的,因为它说明了如何做才能有一个单次计时器。


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

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

    可由select(2)poll(2)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 个字节。我们忽略了它们,但它们包含过期超时的数量

    【讨论】:

    • 为什么在select中做tfd+1?并且初始超时的时间只是第一次运行,之后计时器会占用间隔超时的时间?
    • @zongul67 您可以将多个描述符传递给select。按照设计,该参数必须包含 max fd + 1(在本例中为 tfd+1。谈到超时,您的猜测是正确的。
    【解决方案2】:

    使用 timerfds,其想法是对 fd 的 read 将返回计时器已过期的次数。

    来自 timerfd_settime(2) 手册页:

    对定时器文件描述符进行操作 timerfd_create() 返回的文件描述符支持以下操作:

    阅读(2) 如果计时器自上次修改设置后已经过期一次或多次 timerfd_settime(),或者自上次成功 read(2) 起,然后给 read(2) 的缓冲区返回 一个无符号的 8 字节整数 (uint64_t),包含已发生的过期次数。

    如果在 read(2) 时没有发生计时器到期,则调用任一阻塞 直到下一个计时器到期,如果文件描述符已被删除,则失败并返回错误 EAGAIN 非阻塞(通过使用 fcntl(2) F_SETFL 操作设置 O_NONBLOCK 标志)。

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

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

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

    【讨论】:

      猜你喜欢
      • 2012-09-11
      • 1970-01-01
      • 1970-01-01
      • 2014-02-20
      • 2020-12-04
      • 2012-07-12
      • 2013-01-21
      • 2021-10-24
      • 2011-12-22
      相关资源
      最近更新 更多