Есть ли хорошие временные таймеры POSIX? - программирование
Подтвердить что ты не робот

Есть ли хорошие временные таймеры POSIX?

Вдохновленный последней секундой прыжка, я изучал тайминги (в частности, интервальные таймеры), используя вызовы POSIX.

POSIX предлагает несколько способов настройки таймеров, но все они проблематичны:

  • sleep и nanosleep - это раздражает перезапуск после того, как они прерываются сигналом, и они вводят перекос часов. Вы можете избежать некоторых, но не всех, этого перекоса с некоторой дополнительной работой, но эти функции используют часы реального времени, поэтому это не лишено недостатков.
  • setitimer или более современные timer_settime -это предназначены для интервальных таймеров, но они предназначены для каждого процесса, что является проблемой, если вам нужно несколько активных таймеров. Они также не могут использоваться синхронно, но это менее важно.
  • clock_gettime и clock_nanosleep кажутся правильным ответом при использовании с CLOCK_MONOTONIC. clock_nanosleep поддерживает абсолютные таймауты, поэтому вы можете просто спать, увеличивать время ожидания и повторять. Это легко перезапустить и после перерыва. К сожалению, эти функции также могут быть специфичными для Linux: нет поддержки для них в Mac OS X или FreeBSD.
  • pthread_cond_timedwait доступен на Mac и может работать с gettimeofday как временное решение, но на Mac он может работать только с часами реального времени, поэтому он подвержен неправильному поведению, когда установлены системные часы или скачок второй происходит.

Есть ли API, который мне не хватает? Есть ли разумно переносимый способ создания корректных интервальных таймеров в UNIX-подобных системах или это суммирует сегодняшнее состояние?

Хорошо себя вести и разумно переносить, я имею в виду:

  • Не склонен к перекосу часов (минус, конечно, собственный косой сигнал системы)
  • Устойчив к установленным системным часам или появлению второй секунды
  • Возможность поддержки нескольких таймеров в одном процессе
  • Доступно, по крайней мере, для Linux, Mac OS X и FreeBSD.

Заметка о прыжках секунд (в ответ на R.. answer):

Дни POSIX составляют ровно 86 400 секунд, но дни реального мира редко могут быть длиннее или короче. Как система разрешает это несоответствие, определяется реализацией, но обычно для второго прыжка используется одна и та же временная метка UNIX, как и предыдущая секунда. См. Также: Секундомер и что с ним делать.

Ошибка второго ядра Linux в ярости была результатом неудачной работы после установки часов назад: https://lkml.org/lkml/2012/7/1/203. Даже без этой ошибки часы бы отскочили назад на одну секунду.

4b9b3361

Ответ 1

kqueue и kevent могут быть использованы для этой цели. OSX 10.6 и FreeBSD 8.1 добавляют поддержку EVFILT_USER, которую мы можем использовать для пробуждения цикла событий из другого потока.

Обратите внимание: если вы используете это для реализации своего собственного условия и timedwait, вам не нужны блокировки, чтобы избежать условий гонки, в отличие от

Источники:

Пример кода

Скомпилировать с помощью clang -o test -std=c99 test.c

#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

// arbitrary number used for the identifier property
const int NOTIFY_IDENT = 1337;

static int kq;

static void diep(const char *s) {
   perror(s);
   exit(EXIT_FAILURE);
}

static void *run_thread(void *arg) {
    struct kevent kev;
    struct kevent out_kev;
    memset(&kev, 0, sizeof(kev));
    kev.ident = NOTIFY_IDENT;
    kev.filter = EVFILT_USER;
    kev.flags = EV_ADD | EV_CLEAR;

    struct timespec timeout;
    timeout.tv_sec = 3;
    timeout.tv_nsec = 0;

    fprintf(stderr, "thread sleep\n");

    if (kevent(kq, &kev, 1, &out_kev, 1, &timeout) == -1)
        diep("kevent: waiting");

    fprintf(stderr, "thread wakeup\n");

    return NULL;
}

int main(int argc, char **argv) {
    // create a new kernel event queue
    kq = kqueue();
    if (kq == -1)
        diep("kqueue()");


    fprintf(stderr, "spawn thread\n");
    pthread_t thread;
    if (pthread_create(&thread, NULL, run_thread, NULL))
        diep("pthread_create");

    if (argc > 1) {
        fprintf(stderr, "sleep for 1 second\n");
        sleep(1);
        fprintf(stderr, "wake up thread\n");

        struct kevent kev;
        struct timespec timeout = { 0, 0 };

        memset(&kev, 0, sizeof(kev));
        kev.ident = NOTIFY_IDENT;
        kev.filter = EVFILT_USER;
        kev.fflags = NOTE_TRIGGER;

        if (kevent(kq, &kev, 1, NULL, 0, &timeout) == -1)
            diep("kevent: triggering");
    } else {
        fprintf(stderr, "not waking up thread, pass --wakeup to wake up thread\n");
    }

    pthread_join(thread, NULL);
    close(kq);
    return EXIT_SUCCESS;
}

Выход

$ time ./test
spawn thread
not waking up thread, pass --wakeup to wake up thread
thread sleep
thread wakeup

real    0m3.010s
user    0m0.001s
sys 0m0.002s

$ time ./test --wakeup
spawn thread
sleep for 1 second
thread sleep
wake up thread
thread wakeup

real    0m1.010s
user    0m0.002s
sys 0m0.002s

Ответ 2

Таймеры POSIX (timer_create) не требуют сигналов; вы также можете договориться о том, чтобы истечение срока действия таймера было доставлено в поток через тип уведомления SIGEV_THREAD. К сожалению, реализация glibc фактически создает новый поток для каждого истечения срока действия (который имеет много накладных расходов и разрушает любую надежду на надежность в реальном времени), несмотря на то, что стандарт позволяет повторно использовать один и тот же поток для каждого истечения срока.

Короче говоря, я бы просто рекомендовал создать собственный поток, который использует clock_nanosleep с TIMER_ABSTIME и CLOCK_MONOTONIC для таймера интервала. Поскольку вы упомянули о том, что некоторым сломанным системам может не хватать этих интерфейсов, вы могли бы просто иметь встроенную реализацию (основанную, например, на pthread_cond_timedwait) на таких системах, и полагать, что она может быть менее качественной из-за отсутствия монотонных часов, но это это просто фундаментальное ограничение использования низкокачественной реализации, такой как MacOSX.

Что касается вашей озабоченности в отношении секунд прыжка, если ntpd или аналогичные делают ваши часы в реальном времени скачками назад, когда происходит прыжок, это серьезная ошибка в ntpd. Время POSIX (в секундах с эпохи) находится в единицах календарных секунд (ровно 1/86400 в день) в стандарте, а не в секундах SI, и, таким образом, единственная логика второго прыжка места принадлежит системе POSIX (если есть) mktime/gmtime/localtime, когда они преобразуются между time_t и временем срыва. Я не следил за ошибками, которые попали на этот раз, но они, похоже, были результатом того, что системное программное обеспечение делает много глупого и неправильного материала, а не из какой-либо фундаментальной проблемы.

Ответ 3

Вы можете посмотреть здесь для эмуляции clock_gettime, на которую я также ответил, но также помог мне. Недавно я добавил простой таймер в небольшой репозиторий, который я сохраняю для синхронизации Mac OS X, который частично эмулирует вызовы POSIX. Простой тест запускает таймер на частоте 2000 Гц. Репо называется PosixMachTiming. Попробуйте.

PosixMachTiming основан на Mach. Похоже, что некоторые из API Mach API, связанные с хронометражами, исчезли с страниц Apple и устарели, но все еще есть фрагменты исходного кода, плавающие вокруг. Похоже, что AbsoluteTime единиц и абстракции ядра, найденные здесь, - это новый способ делать вещи. В любом случае PosixMachTiming repo все еще работает для меня.

Обзор PosixMachTiming

clock_gettime эмулируется для CLOCK_REALTIME вызовами функции mach, которые подключаются к системным часам реального времени, получившим название CALENDAR_CLOCK.

clock_gettime эмулируется для CLOCK_MONOTONIC с использованием глобальной переменной (extern mach_port_t clock_port). Эти часы инициализируются, когда компьютер включается или может просыпаться. Я не уверен. В любом случае, это глобальная переменная, вызываемая функцией mach_absolute_time().

clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, ...) эмулируется с использованием nanosleep разности между текущим временем и абсолютным монотонным временем.

itimer_start() и itimer_step() основаны на вызове clock_nanosleep для целевого абсолютного монотонного времени. Он увеличивает целевое время на шаг времени на каждой итерации (а не на текущее время), так что перекос часов не является проблемой.

Обратите внимание, что это не удовлетворяет вашим требованиям, чтобы иметь возможность поддерживать несколько таймеров в одном и том же процессе.