TIMERFD_CREATE(2)

ИМЯ
timerfd_create, timerfd_settime, timerfd_gettime - таймеры, уведомляющие через файловые дескрипторы

ОБЗОР
#include <sys/timerfd.h>

int timerfd_create(int clockid, int flags);

int timerfd_settime(int fd, int flags,
                    const struct itimerspec *new_value,
                    struct itimerspec *old_value);

int timerfd_gettime(int fd, struct itimerspec *curr_value);

ОПИСАНИЕ
Эти системные вызовы создают и работают с таймером, доставляя уведомления о срабатывании через файловый дескриптор. Они предоставляют альтернативу setitimer(2) или timer_create(2); их преимущество в том, что за файловым дескриптором можно следить с помощью select(2), poll(2) и epoll(7).

Использование данных трёх системных вызовов аналогично timer_create(2), timer_settime(2) и timer_gettime(2) (аналога timer_getoverrun(2) нет, так как его возможности предоставляет read(2) как описано ниже).

  timerfd_create()
Вызов timerfd_create() создаёт новый объект таймера и возвращает файловый дескриптор, который ссылается на этот таймер. В аргументе clockid задаются часы, которые используются для хода таймера; значением должно быть одно из следующих:

CLOCK_REALTIME
    Настраиваемые системные часы реального времени.

CLOCK_MONOTONIC
    Ненастраиваемые, постоянно идущие вперёд часы, отсчитывающие время с некоторой неопределённой точки в прошлом, которая не изменяется с момент запуска системы.

CLOCK_BOOTTIME (начиная с Linux 3.15)
    Подобно CLOCK_MONOTONIC, представляет монотонно растущие часы. Однако, если часы CLOCK_MONOTONIC не отсчитывают время когда система находится в состоянии ожидания (suspended), а часы CLOCK_BOOTTIME учитывают время в таком состоянии системы. Это полезно приложениям, которым необходимо учитывать состояние ожидания. CLOCK_REALTIME не подходят для таких приложений, так как эти часы подвержены скачкообразным изменениям системных часов.

CLOCK_REALTIME_ALARM (начиная с Linux 3.11)
    Эти часы подобны CLOCK_REALTIME, но разбудят систему, если она находится с состоянии ожидания. Для установки таймера по этим часам вызывающий должен иметь мандат CAP_WAKE_ALARM.

CLOCK_BOOTTIME_ALARM (начиная с Linux 3.11)
    Эти часы подобны CLOCK_BOOTTIME, но разбудят систему, если она находится с состоянии ожидания. Для установки таймера по этим часам вызывающий должен иметь мандат CAP_WAKE_ALARM.

Текущее значение каждого из этих часов можно получить с помощью clock_gettime(2).

Начиная с Linux 2.6.27, для изменения поведения timerfd_create() можно использовать следующие значения flags (через OR):

TFD_NONBLOCK
    Устанавливает флаг состояния файла O_NONBLOCK для нового открываемого файлового дескриптора. Использование данного флага заменяет дополнительные вызовы fcntl(2) для достижения того же результата.

TFD_CLOEXEC
    Устанавливает флаг close-on-exec (FD_CLOEXEC) для нового открытого файлового дескриптора. Смотрите описание флага O_CLOEXEC в open(2) для того, чтобы узнать как это может пригодиться.

До Linux 2.6.26 включительно аргумент flags должен быть равен нулю.

  timerfd_settime()
Вызов timerfd_settime() запускает или останавливает таймер, на который ссылается файловый дескриптор fd.

В аргументе new_value задаётся начальное срабатывание и интервал таймера. Для этого аргумента используется структура itimerspec, содержащая два поля, каждое из которых, в свою очередь, является структурой timespec:

    struct timespec {
        time_t tv_sec;                /* секунды */
        long   tv_nsec;               /* наносекунды */
    };
    struct itimerspec {
        struct timespec it_interval;  /* интервал для периодического
                                         таймера */
        struct timespec it_value;     /* первое срабатывание */
    };

В new_value.it_value задаётся первое срабатывание таймера, в секундах и наносекундах. Установка любого из полей new_value.it_value в ненулевое значение включает таймер. Установка обоих полей new_value.it_value в ноль выключает таймер.

Установка одного или обоих полей new_value.it_interval в ненулевое значение задаёт период, в секундах и наносекундах, периодического срабатывания таймера после первого срабатывания. Если оба поля new_value.it_interval равны нулю, то таймер срабатывает только один раз, во время, указанное в new_value.it_value.

По умолчанию, начальное время срабатывания, задаваемое в new_value, считается относительно текущего времени часов таймера на момент вызова (т. е., в new_value.it_value задаётся время относительно текущего значения часов, заданных в clockid). Использование абсолютной задержки можно включить через аргумент flags.

Аргумент flags является битовой маской, которая может включать следующие значения:

TFD_TIMER_ABSTIME
    Считать new_value.it_value абсолютным значением часов таймера. Таймер сработает, когда значение часов таймера достигнет значения, указанного в new_value.it_value.

TFD_TIMER_CANCEL_ON_SET
    Если этот флаг указан вместе с TFD_TIMER_ABSTIME и часами этого таймера являются CLOCK_REALTIME или CLOCK_REALTIME_ALARM, то помечать этот таймер как отменяемый, если часы реального времени подвергнуться скачкообразному изменению (settimeofday(2), clock_settime(2) или подобными). Когда такие изменения происходят, текущий или будущий вызова read(2) для файлового дескриптора завершается с ошибкой ECANCELED.

Если old_value не равно NULL, то это указатель на структуру itimerspec, и он будет использоваться для возврата текущих на момент вызова настроек таймера; смотрите описание timerfd_gettime() далее.

  timerfd_gettime()
Вызов timerfd_gettime() возвращает в curr_value, которое указывает на структуру itimerspec, текущие настройки таймера, на который ссылается файловый дескриптор fd.

В поле it_value возвращается время до следующего срабатывания таймера. Если оба поля этой структуры равны нулю, то таймер в данный момент не запущен. Это поле всегда содержит относительное значение, независимо от того, был ли указан флаг TFD_TIMER_ABSTIME при настройке таймера.

В поле it_interval возвращается интервал таймера. Если оба поля этой структуры равны нулю, то таймер настроен на однократное срабатывание, на время, заданное в curr_value.it_value.

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

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

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

    Вызов read(2) завершается ошибкой EINVAL, если размер указанного буфера будет меньше 8 байт.

    Если используются часы CLOCK_REALTIME или CLOCK_REALTIME_ALARM, таймер является абсолютным (TFD_TIMER_ABSTIME) и при вызове timerfd_settime() указан флаг TFD_TIMER_CANCEL_ON_SET, то read(2) завершается ошибкой ECANCELED, если часы реального времени подвергнуться скачкообразному изменению (это позволяет читающему приложению обнаружить такие скачкообразные изменения часов).

poll(2), select(2) (и подобные)
    Файловый дескриптор доступен для чтения (в select(2) аргумент readfds; в poll(2) флаг POLLIN), если произошло одно или более срабатываний таймера.

    Файловый дескриптор также поддерживает другие мультиплексные вызовы: pselect(2), ppoll(2) и epoll(7).

ioctl(2)
    Поддерживается следующая команда, относящаяся к timerfd:

    TFD_IOC_SET_TICKS (начиная с Linux 3.17)
        Корректирует количество истечений таймера, которые произошли. Аргументом является указатель на ненулевое 8-байтовое целое (uint64_t*), содержащее новое количество истечений. После установки количества, все ожидающие таймера пробуждаются. Единственная цель данной команды — восстановить истечений для отсечки/восстановления. Данная операция доступна только, если ядро собрано с параметром CONFIG_CHECKPOINT_RESTORE.

close(2)
    Если файловый дескриптор больше не требуется, его нужно закрыть. Когда все файловые дескрипторы, связанные с одним объектом таймера, будут закрыты, таймер выключается и ядро освобождает его ресурсы.

  Поведение при fork(2)
После fork(2) потомки наследуют копию файлового дескриптора, созданного timerfd_create(). Файловый дескриптор потомка ссылается на тот же объект таймера, что и файловый дескриптор его родителя, и операция read(2) в потомке будет возвращать информацию о срабатываниях таймера.

  Поведение при execve(2)
Файловый дескриптор, созданный timerfd_create(), сохраняется при execve(2), и продолжает генерировать срабатывания таймера, если он включён.

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
При успешном выполнении timerfd_create() возвращает новый файловый дескриптор. При ошибке возвращается -1, и errno устанавливается в соответствующее значение.

При успешном выполнении timerfd_settime() и timerfd_gettime() возвращают 0; в случае ошибки возвращается -1, а errno устанавливается в соответствующее значение ошибки.

ОШИБКИ
Вызов timerfd_create() может завершиться со следующими ошибками:

EINVAL
    Аргумент clockid не равен CLOCK_MONOTONIC или CLOCK_REALTIME;

EINVAL
    Неправильное значение flags или, для Linux 2.6.26 и старее, flags не равно 0.

EMFILE
    Было достигнуто ограничение по количеству открытых файловых дескрипторов на процесс.

ENFILE
    Достигнуто максимальное количество открытых файлов в системе.

ENODEV
    Не удалось смонтировать (внутреннее) безымянное устройство inode.

ENOMEM
    Недостаточно памяти ядра для создания таймера.

Вызовы timerfd_settime() и timerfd_gettime() могут завершаться со следующими ошибками:

EBADF
    Значение fd не является правильным файловым дескриптором.

EFAULT
    Указатели new_value, old_value или curr_value являются некорректными.

EINVAL
    Значение fd не является правильным файловым дескриптором timerfd.

Вызов timerfd_settime() также может завершиться со следующими ошибками:

EINVAL
    Значение new_value некорректно инициализировано (одно из tv_nsec вне диапазона от 0 до 999999999).

EINVAL
    Значение flags неверно.

ВЕРСИИ
Данные системные вызовы доступны в Linux начиная с ядра версии 2.6.25. Поддержка в библиотеке glibc появилась в версии 2.8.

СООТВЕТСТВИЕ СТАНДАРТАМ
Данные системные вызовы есть только в Linux.

ДЕФЕКТЫ
В настоящее время timerfd_create() поддерживает только несколько типов идентификаторов часов, поддерживаемых timer_create(2).

ПРИМЕР
Следующая программа создаёт таймер и затем следит за его работой. Программа получает до трёх аргументов из командной строки. В первом аргументе задаётся количество секунд до первого срабатывания таймера. Во втором аргументе задаётся интервал таймера в секундах. В третьем аргументе задаётся сколько программа должна позволить сработать таймеру до завершения. Второй и третий аргументы необязательны.

Следующий сеанс работы в оболочке показывает использование программы:

    $ a.out 3 1 100
    0.000: таймер запущен
    3.000: read: 1; всего=1
    4.000: read: 1; всего=2
    ^Z                  # нажато control-Z для приостанова программы
    [1]+  Stopped                 ./timerfd3_demo 3 1 100
    $ fg                # возобновление выполнения после нескольких
                           #секунд
    a.out 3 1 100
    9.660: read: 5; всего=7
    10.000: read: 1; всего=8
    11.000: read: 1; всего=9
    ^C                  # нажато control-C для приостанова программы

  Исходный код программы
    #include <sys/timerfd.h>
    #include <time.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <stdint.h>        /* определение uint64_t */
    #define handle_error(msg) \
            do { perror(msg); exit(EXIT_FAILURE); } while (0)
    static void
    print_elapsed_time(void)
    {
        static struct timespec start;
        struct timespec curr;
        static int first_call = 1;
        int secs, nsecs;
        if (first_call) {
            first_call = 0;
            if (clock_gettime(CLOCK_MONOTONIC, &start) == -1)
                handle_error("clock_gettime");
        }
        if (clock_gettime(CLOCK_MONOTONIC, &curr) == -1)
            handle_error("clock_gettime");
        secs = curr.tv_sec - start.tv_sec;
        nsecs = curr.tv_nsec - start.tv_nsec;
        if (nsecs < 0) {
            secs--;
            nsecs += 1000000000;
        }
        printf("%d.%03d: ", secs, (nsecs + 500000) / 1000000);
    }
    int
    main(int argc, char *argv[])
    {
        struct itimerspec new_value;
        int max_exp, fd;
        struct timespec now;
        uint64_t exp, tot_exp;
        ssize_t s;
        if ((argc != 2) && (argc != 4)) {
            fprintf(stderr, "%s нач-сек [интервал макс-сраб]\n",
                    argv[0]);
            exit(EXIT_FAILURE);
        }
        if (clock_gettime(CLOCK_REALTIME, &now) == -1)
            handle_error("clock_gettime");
        /* создаём абсолютный таймер CLOCK_REALTIME с начальным
           срабатыванием и интервалом, заданными из командной строки */
        new_value.it_value.tv_sec = now.tv_sec + atoi(argv[1]);
        new_value.it_value.tv_nsec = now.tv_nsec;
        if (argc == 2) {
            new_value.it_interval.tv_sec = 0;
            max_exp = 1;
        } else {
            new_value.it_interval.tv_sec = atoi(argv[2]);
            max_exp = atoi(argv[3]);
        }
        new_value.it_interval.tv_nsec = 0;
        fd = timerfd_create(CLOCK_REALTIME, 0);
        if (fd == -1)
            handle_error("timerfd_create");
        if (timerfd_settime(fd, TFD_TIMER_ABSTIME, &new_value, NULL) == -1)
            handle_error("timerfd_settime");
        print_elapsed_time();
        printf("таймер запущен\n");
        for (tot_exp = 0; tot_exp < max_exp;) {
            s = read(fd, &exp, sizeof(uint64_t));
            if (s != sizeof(uint64_t))
                handle_error("read");
            tot_exp += exp;
            print_elapsed_time();
            printf("read: %llu; всего=%llu\n",
                    (unsigned long long) exp,
                    (unsigned long long) tot_exp);
        }
        exit(EXIT_SUCCESS);
    }

СМОТРИТЕ ТАКЖЕ
eventfd(2), poll(2), read(2), select(2), setitimer(2), signalfd(2), timer_create(2), timer_gettime(2), timer_settime(2), epoll(7), time(7)