timerfd и читать

У меня есть приложение, которое периодически (по таймеру) проверяет какое-то хранилище данных.
Вот так:

#include <iostream>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <sys/fcntl.h>
#include <unistd.h>

// EPOLL & TIMER
#include <sys/epoll.h>
#include <sys/timerfd.h>

int main(int argc, char **argv)
{
    /* epoll instance */
    int efd = epoll_create1(EPOLL_CLOEXEC);

    if (efd < 0)
    {
        std::cerr << "epoll_create error: " << strerror(errno) << std::endl;
        return EXIT_FAILURE;
    }

    struct epoll_event ev;
    struct epoll_event events[128];

    /* timer instance */
    int tfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);

    struct timespec ts;
    // first expiration in 3. seconds after program start
    ts.tv_sec = 3;
    ts.tv_nsec = 0;

    struct itimerspec new_timeout;
    struct itimerspec old_timeout;

    bzero(&new_timeout, sizeof(new_timeout));
    bzero(&old_timeout, sizeof(old_timeout));

    // value
    new_timeout.it_value = ts;

    // no interval;
    // timer will be armed in epoll_wait event trigger
    new_timeout.it_interval.tv_sec =
    new_timeout.it_interval.tv_nsec = 0;

    // Add the timer descriptor to epoll.
    if (tfd != -1)
    {
        ev.events = EPOLLIN | EPOLLERR /*| EPOLLET*/;
        ev.data.ptr = &tfd;
        epoll_ctl(efd, EPOLL_CTL_ADD, tfd, &ev);
    }

    int flags = 0;
    if (timerfd_settime(tfd, flags, &new_timeout, &old_timeout) < 0)
    {
       std::cerr << "timerfd_settime error: " << strerror(errno) << std::endl;
    }

    int numEvents = 0;
    int timeout = 0;
    bool checkTimer = false;
    while (1)
    {
        checkTimer = false;
        numEvents = epoll_wait(efd, events, 128, timeout);
        if (numEvents > 0)
        {
            for (int i = 0; i < numEvents; ++i)
            {
                if (events[i].data.ptr == &tfd)
                {
                    std::cout << "timeout" << std::endl;
                    checkTimer = true;
                }
            }
        }
        else if(numEvents == 0)
        {
            continue;
        }
        else
        {
            std::cerr << "An error occured: " << strerror(errno) << std::endl;
        }

        if (checkTimer)
        {
            /* Check data storage */
            uint64_t value;
            ssize_t readBytes;
            //while ( (readBytes = read(tfd, &value, 8)) > 0)
            //{
            //    std::cout << "\tread: '" << value << "'" << std::endl;
            //}
            itimerspec new_timeout;
            itimerspec old_timeout;
            new_timeout.it_value.tv_sec = rand() % 3 + 1;
            new_timeout.it_value.tv_nsec = 0;
            new_timeout.it_interval.tv_sec =
            new_timeout.it_interval.tv_nsec = 0;
            timerfd_settime(tfd, flags, &new_timeout, &old_timeout);
        }
    }

    return EXIT_SUCCESS;
}

Это простое описание моего приложения. После каждого тайм-аута таймер необходимо переустанавливать на какое-то значение, отличающееся в каждом тайм-ауте.

Вопросы:

  1. Нужно ли добавлять timerfd в epoll (epoll_ctl) с флагом EPOLLET?
  2. Нужно ли читать timerfd после каждого таймаута?
  3. Нужно ли epoll_wait бесконечно (timeout = -1)?

person coredumped    schedule 19.09.2012    source источник


Ответы (2)


Вы можете сделать это в одном из двух режимов: по фронту или по уровню. Если вы выберете маршрут, инициируемый краем, то вы должны пройти EPOLLET и не нужно читать timerfd после каждого пробуждения. Тот факт, что вы получаете событие от epoll, означает, что сработало одно или несколько тайм-аутов. При желании вы можете прочитать timerfd, и он вернет количество тайм-аутов, которые сработали с момента последнего чтения.

Если вы выберете маршрут, запускаемый уровнем, вам не нужно проходить EPOLLET, но вы должны читать timerfd после каждого пробуждения. Если вы этого не сделаете, то вас сразу же снова разбудят, пока вы не израсходуете тайм-аут.

Вы должны либо передать -1 в epoll в качестве тайм-аута, либо какое-то положительное значение. Если вы пройдете 0, как в примере, то вы никогда не заснете, вы просто будете крутиться, ожидая, когда истечет время, чтобы выстрелить. Это почти наверняка нежелательное поведение.

person jleahy    schedule 19.09.2012
comment
Похоже, что read() является обязательным в обоих случаях. Без read(), в случае EPOLLET, вы не получите следующее событие (пока не произойдет read()), а без EPOLLET вы будете постоянно получать событие (пока не произойдет read()). - person niry; 18.09.2020

Ответы на вопросы:

  1. Нужно ли добавлять timerfd в epoll (epoll_ctl) с флагом EPOLLET?

Нет. Добавление EPOLLET (краевой триггер) меняет поведение получения событий. Без EPOLLET вы будете постоянно получать события от epoll_wait, связанные с timerfd, пока не получите read() от timerfd. С EPOLLET вы НЕ будете получать дополнительные события помимо первого, даже если произойдет новый срок действия, пока вы не получите read() из timerfd и не произойдет новый срок действия.

  1. Нужно ли читать timerfd после каждого таймаута?

Да, чтобы продолжать и получать события (только) при новом истечении срока действия (см. выше). Нет, когда периодический таймер не используется (только однократное истечение), и вы закрываете timerfd без чтения.

  1. Нужно ли epoll_wait бесконечно (timeout = -1)?

Нет. Вы можете использовать тайм-аут epoll_wait вместо timerfd. Лично я считаю, что проще использовать timerfd, чем продолжать вычислять следующий тайм-аут для EPOLL, особенно если вы ожидаете несколько интервалов тайм-аута; следить за тем, какая у вас следующая задача, когда происходит тайм-аут, намного проще, когда она привязана к конкретному событию, которое проснулось.

person niry    schedule 19.09.2020