How to use timerfd properly?
Asked Answered
R

2

5

I use timerfd with zmq.

How can I use timerfd_create and timerfd_set to wait one second for the timer (https://man7.org/linux/man-pages/man2/timerfd_create.2.html)?

I have looked through the link but I still do not get how I can initilize a timer that waits one second per tick with create and set. This is exactly my task:

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.

In this project we have a function timer _ set _(), where the timer is set with the function timerfd_create and timerfd_settimer(). I hope you can help me.

This is my progress (part of my code):

    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); 
Rodgerrodgers answered 4/8, 2020 at 7:2 Comment(0)
F
9

The question appears about setting correctly the timeouts of the timer.

With the settings

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

You are correctly setting the initial timeout to 1s (field timerValue.it_value). But you are also setting a periodic interval of 1s, and you didn't mention the will to do it.


About the timeouts

This behavior is described by the following passage of the manual:

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.

The emphasis on the last paragraph is mine, as it shows what to do in order to have a single-shot timer.


The benefits of timerrfd. How to detect timer expiration?

The main advantage provided by timerfd is that the timer is associated to a file descriptor, and this means that it

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

The information contained in the other answer about read() is valid as well: let's just say that, even using functions such as select(), read() function will be required in order to consume data in the file descriptor.


A complete example

In the following demonstrative program, a timeout of 4 seconds is set; after that a periodic interval of 5 seconds is set.

The good old select() is used in order to wait for timer expiration, and read() is used to consume data (that is the number of expired timeouts; we will ignore it).

#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 (twice) for periodic 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;
}

And here it is the output. Every row contains also the timestamp, so that the actual elapsed time can be checked:

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

Please note:

  • We just performed 3 reads, for test
  • The intervals are 4s + 5s + 5s (initial timeout + two interval timeouts)
  • 8 bytes are returned by read(). We ignored them, but they contained the number of the expired timeouts
Fromenty answered 4/8, 2020 at 13:34 Comment(2)
Why do you do tfd+1 in select? And the time of initial timeout is just for the first run, and after that the timer takes the time of the interval timeouts?Rodgerrodgers
@zongul67 You can pass to select more than one descriptor. By design that parameter must contain the max fd + 1 (in this case tfd+1. Talking about the timeouts, your guess is correct.Fromenty
S
1

With timerfds, the idea is that a read on the fd will return the number of times the timer has expired.

From the timerfd_settime(2) man page:

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

So, basically, you create an unsigned 8 byte integer (uint64_t on Linux), and pass that to your read call.

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

Something like that, if you want to block until you get an expiry.

Submultiple answered 4/8, 2020 at 8:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.