timerfd_create(2)

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

БИБЛИОТЕКА
Стандартная библиотека языка C (libc, -lc)

ОБЗОР
#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 *_Nullable 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.

See clock_getres(2) for some further details on the above clocks.

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

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

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

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

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

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

The new_value argument specifies the initial expiration and interval for the timer. The itimerspec structure used for this argument is described in itimerspec(3type).

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

  Работа с файловым дескриптором таймера
The file descriptor returned by timerfd_create() supports the following additional 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. (The returned value is in host byte order—that is, the native byte order for integers on the host machine.)

    Если таймер ещё не срабатывал до вызова 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, если часы реального времени подвергнуться скачкообразному изменению (это позволяет читающему приложению обнаружить такие скачкообразные изменения часов).

    If the associated clock is either CLOCK_REALTIME or CLOCK_REALTIME_ALARM, the timer is absolute (TFD_TIMER_ABSTIME), and the flag TFD_TIMER_CANCEL_ON_SET was not specified when calling timerfd_settime(), then a discontinuous negative change to the clock (e.g., clock_settime(2)) may cause read(2) to unblock, but return a value of 0 (i.e., no bytes read), if the clock change occurs after the time expired, but before the read(2) on the file descriptor.

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
    The clockid is not valid.

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

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

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

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

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

EPERM
    clockid was CLOCK_REALTIME_ALARM or CLOCK_BOOTTIME_ALARM but the caller did not have the CAP_WAKE_ALARM capability.

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

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

EFAULT
    Некорректный указатель new_value, old_value или curr_value.

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

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

ECANCELED
    Смотрите ЗАМЕЧАНИЯ.

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

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

СТАНДАРТЫ
Linux.

ИСТОРИЯ
Linux 2.6.25, glibc 2.8.

ПРИМЕЧАНИЯ
Suppose the following scenario for CLOCK_REALTIME or CLOCK_REALTIME_ALARM timer that was created with timerfd_create():

(1) The timer has been started (timerfd_settime()) with the TFD_TIMER_ABSTIME and TFD_TIMER_CANCEL_ON_SET flags;

(2) A discontinuous change (e.g., settimeofday(2)) is subsequently made to the CLOCK_REALTIME clock; and

(3) the caller once more calls timerfd_settime() to rearm the timer (without first doing a read(2) on the file descriptor).

In this case the following occurs:

•   The timerfd_settime() returns -1 with errno set to ECANCELED. (This enables the caller to know that the previous timer was affected by a discontinuous change to the clock.)

•   The timer is successfully rearmed with the settings provided in the second timerfd_settime() call. (This was probably an implementation accident, but won't be fixed now, in case there are applications that depend on this behavior.)

ОШИБКИ
В настоящее время timerfd_create() поддерживает только несколько типов идентификаторов часов, поддерживаемых timer_create(2).

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

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

    $ a.out 3 1 100
    0.000:	timer started
    3.000:	read: 1; total=1
    4.000:	read: 1; total=2
    ^Z                  # type control-Z to suspend the program
    [1]+  Stopped                 ./timerfd3_demo 3 1 100
    $ fg                # Resume execution after a few seconds
    a.out 3 1 100
    9.660:	read: 5; total=7
    10.000:	read: 1; total=8
    11.000:	read: 1; total=9
    ^C                  # type control-C to suspend the program

  Исходный код программы
    #include <err.h>
    #include <inttypes.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/timerfd.h>
    #include <sys/types.h>
    #include <time.h>
    #include <unistd.h>

    static void
    print_elapsed_time(void)
    {
        int                     secs, nsecs;
        static int              first_call = 1;
        struct timespec         curr;
        static struct timespec  start;

        if (first_call) {
            first_call = 0;
            if (clock_gettime(CLOCK_MONOTONIC, &start) == -1)
                err(EXIT_FAILURE, "clock_gettime");
        }

        if (clock_gettime(CLOCK_MONOTONIC, &curr) == -1)
            err(EXIT_FAILURE, "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:\t", secs, (nsecs + 500000) / 1000000);
    }

    int
    main(int argc, char *argv[])
    {
        int                fd;
        ssize_t            s;
        uint64_t           expir, tot_expir, max_expir;
        struct timespec    now;
        struct itimerspec  new_value;

        if (argc != 2 && argc != 4) {
            fprintf(stderr, "%s init-secs [interval-secs max-num-expir]\n",
                    argv[0]);
            exit(EXIT_FAILURE);
        }

        if (clock_gettime(CLOCK_REALTIME, &now) == -1)
            err(EXIT_FAILURE, "clock_gettime");

        /* Create a CLOCK_REALTIME absolute timer with initial
           expiration and interval as specified in command line.  */

        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_expir = 1;
        } else {
            new_value.it_interval.tv_sec = atoi(argv[2]);
            max_expir = atoi(argv[3]);
        }
        new_value.it_interval.tv_nsec = 0;

        fd = timerfd_create(CLOCK_REALTIME, 0);
        if (fd == -1)
            err(EXIT_FAILURE, "timerfd_create");

        if (timerfd_settime(fd, TFD_TIMER_ABSTIME, &new_value, NULL) == -1)
            err(EXIT_FAILURE, "timerfd_settime");

        print_elapsed_time();
        printf("timer started\n");

        for (tot_expir = 0; tot_expir < max_expir;) {
            s = read(fd, &expir, sizeof(uint64_t));
            if (s != sizeof(uint64_t))
                err(EXIT_FAILURE, "read");

            tot_expir += expir;
            print_elapsed_time();
            printf("read: %" PRIu64 "; total=%" PRIu64 "\n",
                   expir, tot_expir);
        }

        exit(EXIT_SUCCESS);
    }

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

ПЕРЕВОД
Русский перевод этой страницы руководства разработал(и) Azamat Hackimov <azamat.hackimov@gmail.com>, Dmitry Bolkhovskikh <d20052005@yandex.ru>, Yuri Kozlov <yuray@komyakino.ru> и Иван Павлов <pavia00@gmail.com>

Этот перевод является свободной программной документацией; он распространяется на условиях общедоступной лицензии GNU (GNU General Public License - GPL, https://www.gnu.org/licenses/gpl-3.0.html версии 3 или более поздней) в отношении авторского права, но БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ.

Если вы обнаружите какие-либо ошибки в переводе этой страницы руководства, пожалуйста, сообщите об этом разработчику(ам) по его(их) адресу(ам) электронной почты или по адресу списка рассылки русских переводчиков (<debian-l10n-russian@lists.debian.org>).