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_REALTIME — регулируемые системные часы. CLOCK_MONOTONIC — нерегулируемые часы, на которые не влияют скачкообразные изменения системных часов (например, ручное изменение системного времени). Текущее значение каждых часов можно получить с помощью 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 задаётся начальное срабатывание и интервал таймера. Для этого аргумента используется структура itimer, содержащая два поля, каждое из которых, в свою очередь, является структурой 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. Значение аргумента flags может быть 0 — для запуска относительного таймера (в new_value.it_value задаётся время относительно текущего значения часов, заданных в clockid), или TFD_TIMER_ABSTIME — для запуска абсолютного таймера (в new_value.it_value задаётся абсолютное время часов, заданных в clockid; то есть таймер сработает, когда значение часов будет равно значению new_value.it_value). Если 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 байт.

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)