timer_create(2)

ИМЯ
timer_create - создаёт таймер POSIX для определённого процесса

LIBRARY
Real-time library (librt, -lrt)

СИНТАКСИС
#include <signal.h>           /* определения констант SIGEV_* */
#include <time.h>

int timer_create(clockid_t clockid,
                 struct sigevent *_Nullable restrict sevp,
                 timer_t *restrict timerid);

    Требования макроса тестирования свойств для glibc (см. feature_test_macros(7)):

timer_create():
    _POSIX_C_SOURCE >= 199309L

ОПИСАНИЕ
Вызов timer_create() создаёт новый таймер для процесса. Идентификатор нового таймера возвращается в буфере, указанном в timerid, его значение не должно быть равно null. Данный идентификатор уникален для процесса, пока таймер не будет удалён. Новый таймер создаётся неактивным.

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

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

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

CLOCK_PROCESS_CPUTIME_ID (начиная с Linux 2.6.12)
    Часы, измеряющие время ЦП (пользовательское и системное), затраченное вызывающим процессом (всеми его нитями).

CLOCK_THREAD_CPUTIME_ID (начиная с Linux 2.6.12)
    Часы, измеряющие время ЦП (пользовательское и системное), затраченное вызывающей нитью.

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

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

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

CLOCK_TAI (начиная с Linux 3.10)
    A system-wide clock derived from wall-clock time but ignoring leap seconds.

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

Помимо значений, перечисленных ранее, в clockid может быть указано clockid, возвращённое вызовом clock_getcpuclockid(3) или pthread_getcpuclockid(3).

Аргумент sevp указывает на структуру sigevent, которая задаёт способ уведомления вызывающего при срабатывании таймера. Определение и описание структуры смотрите в sigevent(7).

В поле sevp.sigev_notify можно указать следующие значения:

SIGEV_NONE
    Выполнять синхронное уведомление при срабатывании таймера. Ход таймера можно отслеживать с помощью timer_gettime(2).

SIGEV_SIGNAL
    При срабатывании таймера генерировать для процесса сигнал sigev_signo. Подробности смотрите в sigevent(7). Полю si_code структуры siginfo_t присваивается значение SI_TIMER. В любой момент времени для таймера в очередь процесса ставится не более одного сигнала; подробности смотрите в timer_getoverrun(2).

SIGEV_THREAD
    При срабатывании вызвать sigev_notify_function, как если бы это была начальная функция новой нити. Подробности смотрите в sigevent(7).

SIGEV_THREAD_ID (есть только в Linux)
    Как для SIGEV_SIGNAL, но сигнал нацелен на нить, чей ID указывается в sigev_notify_thread_id, который должен быть нитью того же процесса что и вызывающий. В поле sigev_notify_thread_id указывается ID ядерной нити, то есть значение, возвращаемое clone(2) или gettid(2). Этот флаг предназначен только для использования в библиотеках нитей.

Указание в sevp значения NULL эквивалентно указанию указателя на структуру sigevent, в которой sigev_notify равно SIGEV_SIGNAL, sigev_signo равно SIGALRM и sigev_value.sival_int равно ID таймера.

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

ОШИБКИ

EAGAIN
    Временная ошибка, на время выделения ядром структур таймера.

EINVAL
    Некорректный ID часов, sigev_notify, sigev_signo или sigev_notify_thread_id.

ENOMEM
    Невозможно выделить память.

ENOTSUP
    The kernel does not support creating a timer against this clockid.

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

ВЕРСИИ
Данный системный вызов появился в Linux 2.6.

СТАНДАРТЫ
POSIX.1-2001, POSIX.1-2008.

ЗАМЕЧАНИЯ
С помощью timer_create() программа может создавать несколько интервальных таймеров.

Таймеры не наследуются в потомке после fork(2), и выключаются и удаляются при execve(2).

Ядро заранее выделяет «сигнал реального времени в очереди» для каждого таймера, создаваемого timer_create(). В результате, количество таймеров ограничено ресурсом RLIMIT_SIGPENDING (смотрите setrlimit(2)).

Таймеры, созданные timer_create(), часто называют «(интервальными) таймерами POSIX». Программный интерфейс таймеров POSIX состоит из следующих интерфейсов:

timer_create()
    Create a timer.

timer_settime(2)
    Arm (start) or disarm (stop) a timer.

timer_gettime(2)
    Fetch the time remaining until the next expiration of a timer, along with the interval setting of the timer.

timer_getoverrun(2)
    Return the overrun count for the last timer expiration.

timer_delete(2)
    Disarm and delete a timer.

Начиная с Linux 3.10, файл /proc/pid/timers можно использовать для просмотра списка таймеров POSIX для процесса с PID равным pid. Подробности смотрите в proc(5).

Начиная с Linux 4.10, поддержка таймеров POSIX теперь необязательна и включена по умолчанию. Поддержку в ядре можно выключить через параметр CONFIG_POSIX_TIMERS.

  Отличия между библиотекой C и ядром
Частично, реализация программного интерфейса таймеров POSIX предоставляется glibc. А именно:

•   Большая часть функций для SIGEV_THREAD реализована в glibc, а не в ядре (это необходимо, так как в обработку уведомления вовлечена нить, которая должна управляться библиотекой C, реализующей нити POSIX). Хотя уведомление доставляется процессу через нить, внутри реализации NPTL для SIGEV_THREAD_ID используется значение sigev_notify и сигнал реального времени, который зарезервирован для реализации (смотрите nptl(7)).

•   Стандартная ситуация, когда evp равно NULL, обрабатывается в glibc, где вызывается нижележащий системный вызов с заполненной подходящим образом структурой sigevent.

•   Идентификаторы таймеров, обрабатываемые на уровне пользователя, поддерживаются glibc, которая отображает эти ID в ID таймеров, созданных ядром.

The POSIX timers system calls first appeared in Linux 2.6. Prior to this, glibc provided an incomplete user-space implementation (CLOCK_REALTIME timers only) using POSIX threads, and before glibc 2.17, the implementation falls back to this technique on systems running kernels older than Linux 2.6.

ПРИМЕРЫ
Программа ниже обрабатывает два аргумента: интервал сна в секундах и частоту таймера в наносекундах. Программа устанавливает обработчик сигнала для таймера, блокирует этот сигнал, создаёт и включает таймер, который срабатывает с заданной частотой, засыпает на указанное количество секунд, а после разблокирует сигнал таймера. Предполагая, что таймер сработает не менее одного раза пока программа спит, обработчик сигнала будет вызван и покажет некоторую информацию об уведомлении таймера. Программа завершается после одного вызова обработчика сигнала.

В следующем примере программа спит 1 секунду после создания таймера, который работает с частотой 100 наносекунд. За время разблокировки и доставки сигнала, произошло около 10 миллионов переполнений.

    ./$ ./a.out 1 100
    Устанавливается обработчик сигнала 34
    Блокируется сигнал 34
    ID таймера — 0x804c008
    Спим 1 секунду
    Разблокируется сигнал 34
    Пойман сигнал 34
        sival_ptr = 0xbfb174f4;     *sival_ptr = 0x804c008
        счётчик переполнения = 10004886

  Исходный код программы
    #include <signal.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    #include <unistd.h>

    #define CLOCKID CLOCK_REALTIME
    #define SIG SIGRTMIN

    #define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
                            } while (0)

    static void
    print_siginfo(siginfo_t *si)
    {
        int      or;
        timer_t  *tidp;

        tidp = si->si_value.sival_ptr;

        printf("    sival_ptr = %p; ", si->si_value.sival_ptr);
        printf("    *sival_ptr = %#jx\n", (uintmax_t) *tidp);

        or = timer_getoverrun(*tidp);
        if (or == -1)
            errExit("timer_getoverrun");
        else
            printf("    счётчик переполнения = %d\n", or);
    }

    static void
    handler(int sig, siginfo_t *si, void *uc)
    {
        /* Замечание: вызов printf() из обработчика сигнала небезопасен
           (и не должен выполняться в готовых программах), так как
           printf() не async-signal-safe; смотрите signal-safety(7).
           Тем не менее, здесь мы используем printf(), так как это простой
           способ показать когда вызывается обработчик. */

        printf("Пойман сигнал %d\n", sig);
        print_siginfo(si);
        signal(sig, SIG_IGN);
    }

    int
    main(int argc, char *argv[])
    {
        timer_t            timerid;
        sigset_t           mask;
        long long          freq_nanosecs;
        struct sigevent    sev;
        struct sigaction   sa;
        struct itimerspec  its;

        if (argc != 3) {
            fprintf(stderr, "Использование: %s <secs> <nsecs>\n",
                    argv[0]);
            exit(EXIT_FAILURE);
        }

        /* Устанавливаем обработчик для сигнала таймера. */

        printf("Устанавливается обработчик сигнала %d\n", SIG);
        sa.sa_flags = SA_SIGINFO;
        sa.sa_sigaction = handler;
        sigemptyset(&sa.sa_mask);
        if (sigaction(SIG, &sa, NULL) == -1)
            errExit("sigaction");

        /* Временно блокируем сигнал таймера. */

        printf("Блокируется сигнал %d\n", SIG);
        sigemptyset(&mask);
        sigaddset(&mask, SIG);
        if (sigprocmask(SIG_SETMASK, &mask, NULL) == -1)
            errExit("sigprocmask");

        /* Создаём таймер. */

        sev.sigev_notify = SIGEV_SIGNAL;
        sev.sigev_signo = SIG;
        sev.sigev_value.sival_ptr = &timerid;
        if (timer_create(CLOCKID, &sev, &timerid) == -1)
            errExit("timer_create");

        printf("ID таймера %#jx\n", (uintmax_t) timerid);

        /* Запускаем таймер. */

        freq_nanosecs = atoll(argv[2]);
        its.it_value.tv_sec = freq_nanosecs / 1000000000;
        its.it_value.tv_nsec = freq_nanosecs % 1000000000;
        its.it_interval.tv_sec = its.it_value.tv_sec;
        its.it_interval.tv_nsec = its.it_value.tv_nsec;

        if (timer_settime(timerid, 0, &its, NULL) == -1)
             errExit("timer_settime");

        /* Ненадолго засыпаем; за это время, таймер может сработать
           несколько раз. */

        printf("Спим %d секунду\n", atoi(argv[1]));
        sleep(atoi(argv[1]));

        /* Разблокируем сигнал таймера, чтобы доставлялись
           уведомления таймера. */

        printf("Разблокируется сигнал %d\n", SIG);
        if (sigprocmask(SIG_UNBLOCK, &mask, NULL) == -1)
            errExit("sigprocmask");

        exit(EXIT_SUCCESS);
    }

СМ. ТАКЖЕ
clock_gettime(2), setitimer(2), timer_delete(2), timer_getoverrun(2), timer_settime(2), timerfd_create(2), clock_getcpuclockid(3), pthread_getcpuclockid(3), pthreads(7), sigevent(7), signal(7), time(7)

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

Этот перевод является бесплатной документацией; прочитайте Стандартную общественную лицензию GNU версии 3 (https://www.gnu.org/licenses/gpl-3.0.html) или более позднюю, чтобы узнать об условиях авторского права. Мы не несем НИКАКОЙ ОТВЕТСТВЕННОСТИ.

Если вы обнаружите ошибки в переводе этой страницы руководства, пожалуйста, отправьте электронное письмо на man-pages-ru-talks@lists.sourceforge.net.