Как правильно использовать timerfd? - PullRequest
1 голос
/ 04 августа 2020

Я использую timerfd с zmq.

Как я могу использовать timerfd_create и timerfd_set, чтобы подождать одну секунду для таймера (https://man7.org/linux/man-pages/man2/timerfd_create.2.html)?

Я просмотрел ссылку, но до сих пор не понимаю, как я могу инициализировать таймер, ожидающий одну секунду за такт, с помощью create и set. Это в точности моя задача:

Мы запускаем таймер с timerfd_create(), что составляет 1 / se c. тиканье. При установке таймера с помощью 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); 

Ответы [ 2 ]

1 голос
/ 04 августа 2020

Возникает вопрос о правильной установке таймаутов таймера.

С настройками

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). Но вы также устанавливаете период c интервал из единиц, и вы не упомянули о желании сделать это.

О тайм-аутах

Это поведение описано в следующем отрывке из руководства:

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 секунды; после этого устанавливается интервал periodi c в 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 чтения для теста
  • Интервалы: 4 с + 5 с + 5 с (начальный тайм-аут + два тайм-аута)
  • 8 байт возвращаются read(). Мы проигнорировали их, но они содержали количество истекших тайм-аутов
1 голос
/ 04 августа 2020

С timerfds идея состоит в том, что read на fd вернет количество раз, когда истек таймер.

Из timerfd_settime (2) страница руководства:

Работа с файловым дескриптором таймера Дескриптор файла, возвращаемый функцией timerfd_create (), поддерживает следующие операции:

read (2) Если таймер уже истек, один или несколько раз с момента последнего изменения его настроек с помощью timerfd_settime () или с момента последнего успешного чтения (2), тогда буфер, предоставленный для чтения (2), возвращает 8-байтовое целое число без знака (uint64_t), содержащее количество истечений, которые произошли.

Если во время чтения (2) не произошло истечения срока таймера, то вызов либо блокируется до следующего истечения таймера, либо завершается ошибкой EAGAIN, если дескриптор файла имеет был сделан неблокирующим (с помощью операции fcntl (2) F_SETFL для установки флага O_NONBLOCK).

Итак, по сути, вы создаете беззнаковый 8 байтовое целое число (uint64_t на Linux) и передайте его вашему вызову чтения.

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

Что-то вроде этого, если вы хотите заблокировать до истечения срока действия.

...