Разница между CLOCK_REALTIME и CLOCK_MONOTONIC?

Не могли бы вы объяснить разницу между CLOCK_REALTIME а также CLOCK_MONOTONIC часы, возвращенные clock_gettime() в линуксе?

Какой вариант лучше выбрать, если мне нужно вычислить прошедшее время между временными метками, созданными внешним источником, и текущим временем?

Наконец, если у меня есть демон NTP, периодически меняющий системное время, как эти настройки взаимодействуют с каждым из CLOCK_REALTIME а также CLOCK_MONOTONIC?

9 ответов

Решение

CLOCK_REALTIME представляет предположение машины о текущих настенных часах и времени суток. Как говорят Игнасио и МаркР, это означает, что CLOCK_REALTIME может перемещаться вперед и назад при изменении системных часов времени, в том числе по NTP.

CLOCK_MONOTONIC представляет абсолютное истекшее время настенных часов с некоторой произвольной фиксированной точки в прошлом. На него не влияют изменения системных часов времени.

Если вы хотите вычислить истекшее время между двумя событиями, наблюдаемыми на одной машине, без промежуточной перезагрузки, CLOCK_MONOTONIC это лучший вариант.

Обратите внимание, что в Linux CLOCK_MONTONIC не измеряет время, потраченное на приостановку, хотя по определению POSIX это должно быть. Вы можете использовать для Linux CLOCK_BOOTTIME для монотонных часов, которые продолжают работать в режиме ожидания.

Книга Роберта Лава, посвященная системному программированию LINUX, 2-е издание, конкретно рассматривает ваш вопрос в начале главы 11, стр. 363:

Важным аспектом монотонного источника времени является НЕ текущее значение, а гарантия того, что источник времени строго линейно увеличивается, и, следовательно, полезен для расчета разницы во времени между двумя выборками.

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

CLOCK_REALTIME зависит от NTP, и может двигаться вперед и назад. CLOCK_MONOTONIC нет и продвигается на один тик за тик.

POSIX 7 указывает оба на http://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html:

CLOCK_REALTIME:

Эти часы представляют часы, измеряющие реальное время для системы. Для этих часов значения, возвращаемые функцией clock_gettime() и определяемые функцией clock_settime(), представляют количество времени (в секундах и наносекундах) с начала эпохи.

CLOCK_MONOTONIC (дополнительная функция):

Для этих часов значение, возвращаемое функцией clock_gettime(), представляет количество времени (в секундах и наносекундах) с неопределенной точки в прошлом (например, время запуска системы или эпоха). Эта точка не изменяется после запуска системы. Значение часов CLOCK_MONOTONIC не может быть установлено с помощью clock_settime().

clock_settime() дает важный совет: системы POSIX могут произвольно изменять CLOCK_REALITME с этим, так что не полагайтесь на то, что он течет ни непрерывно, ни вперед. NTP может быть реализован с использованием clock_settime()и мог повлиять только CLOCK_REALITME,

Реализация ядра Linux, похоже, занимает время загрузки как эпоху CLOCK_MONOTONIC: Начальная точка для CLOCK_MONOTONIC

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

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

Представьте, что происходит, когда вы приостанавливаете свой ноутбук - CLOCK_REALTIME прыгает вперед после резюме, CLOCK_MONOTONIC не. Попробуйте это на ВМ.

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

В зависимости от того, как часто вы будете звонить clock_gettime(), вы должны иметь в виду, что только некоторые из "тактовых импульсов" предоставляются Linux в VDSO (т.е. не требуют системного вызова со всеми накладными расходами одного - что только ухудшилось, когда Linux добавил защиту для защиты от Spectre- вроде атак).

Пока clock_gettime(CLOCK_MONOTONIC,...), clock_gettime(CLOCK_REALTIME,...), а также gettimeofday()всегда будет очень быстро (ускорен VDSO), это не относится к, например, CLOCK_MONOTONIC_RAW или любой из других POSIX часов.

Это может измениться в зависимости от версии ядра и архитектуры.

Хотя большинству программ не нужно обращать на это внимание, могут быть всплески задержки в тактовых частотах, ускоренных VDSO: если вы ударите их точно, когда ядро ​​обновляет область общей памяти с помощью счетчиков часов, ему придется дождаться ядро доделать.

Вот "доказательство" (GitHub, чтобы держать ботов подальше от kernel.org):https://github.com/torvalds/linux/commit/2aae950b21e4bc789d1fc6668faf67e8748300b7

Есть одно большое различие между CLOCK_REALTIME и MONOTONIC. CLOCK_REALTIME может переходить вперед или назад в соответствии с NTP. По умолчанию NTP позволяет ускорить или замедлить тактовую частоту до 0,05%, но NTP не может заставить монотонные часы прыгать вперед или назад.

Я хотел бы уточнить, что означает «система приостановлена» в этом контексте.

я читаюtimefd_createи со страницы руководства https://man7.org/linux/man-pages/man2/timerfd_create.2.html .

          CLOCK_BOOTTIME (Since Linux 3.15)
          Like CLOCK_MONOTONIC, this is a monotonically increasing
          clock.  However, whereas the CLOCK_MONOTONIC clock does
          not measure the time while a system is suspended, the
          CLOCK_BOOTTIME clock does include the time during which
          the system is suspended.  This is useful for applications
          that need to be suspend-aware.  CLOCK_REALTIME is not
          suitable for such applications, since that clock is
          affected by discontinuous changes to the system clock.

Исходя из приведенного выше описания, мы можем указать, что иCLOCK_BOOTTIMEпо-прежнему считает время, когда система приостановлена, а не делает.

Я был смущен тем, что именно означает «система приостановлена». Сначала я подумал, что это значит, когда мы отправимCtrl + Zс терминала, что делает процесс приостановленным. Но это не так.

Ответ @MarkR вдохновил меня:

Представьте, что произойдет, если вы приостановите работу своего ноутбука .... Попробуйте на виртуальной машине .

Таким образом, буквально «система приостановлена» означает, что вы переводите свой компьютер в спящий режим.

Тем не менее, считает время, когда компьютер спит.


Сравните вывод этих двух фрагментов кода

timefd_create_realtime_clock.c

копировать изman timefd_create

      #include <sys/timerfd.h>
#include <time.h>
#include <unistd.h>
#include <inttypes.h>      /* Definition of PRIu64 */
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>        /* Definition of 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 init-secs [interval-secs max-exp]\n",
                argv[0]);
        exit(EXIT_FAILURE);
    }

    if (clock_gettime(CLOCK_REALTIME, &now) == -1)
        handle_error("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_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("timer started\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: %" PRIu64 "; total=%" PRIu64 "\n", exp, tot_exp);
    }

    exit(EXIT_SUCCESS);
}

timefd_create_monotonic_clock.c

немного изменить, изменитьCLOCK_REALTIMEкCLOCK_MONOTONIC

      #include <sys/timerfd.h>
#include <time.h>
#include <unistd.h>
#include <inttypes.h>      /* Definition of PRIu64 */
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>        /* Definition of 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 init-secs [interval-secs max-exp]\n",
                argv[0]);
        exit(EXIT_FAILURE);
    }

    // T_NOTE: comment 
    // if (clock_gettime(CLOCK_REALTIME, &now) == -1)
        // handle_error("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;

    new_value.it_value.tv_sec = atoi(argv[1]);
    new_value.it_value.tv_nsec = 0;
    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);
    fd = timerfd_create(CLOCK_MONOTONIC, 0);
    if (fd == -1)
        handle_error("timerfd_create");

    // if (timerfd_settime(fd, TFD_TIMER_ABSTIME, &new_value, NULL) == -1)
    if (timerfd_settime(fd, 0, &new_value, NULL) == -1)
        handle_error("timerfd_settime");

    print_elapsed_time();
    printf("timer started\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: %" PRIu64 "; total=%" PRIu64 "\n", exp, tot_exp);
    }

    exit(EXIT_SUCCESS);
}
  1. скомпилируйте оба и запустите в 2 вкладках в одном терминале
    ./timefd_create_monotonic_clock 3 1 100
    ./timefd_create_realtime_clock 3 1 100

  2. перевести мой рабочий стол Ubuntu в спящий режим

  3. Подождите несколько минут

  4. Разбудите мою Ubuntu, нажав кнопку питания один раз

  5. Проверьте вывод терминала

Выход:

Часы реального времени тут же остановились. Потому что он подсчитал время, прошедшее, когда компьютер был приостановлен / спит.

      tian@tian-B250M-Wind:~/playground/libuv-vs-libevent$ ./timefd_create_realtime_clock 3 1 100
0.000: timer started
3.000: read: 1; total=1
4.000: read: 1; total=2
5.000: read: 1; total=3
6.000: read: 1; total=4
7.000: read: 1; total=5
8.000: read: 1; total=6
9.000: read: 1; total=7
10.000: read: 1; total=8
11.000: read: 1; total=9
12.000: read: 1; total=10
13.000: read: 1; total=11
14.000: read: 1; total=12
15.000: read: 1; total=13
16.000: read: 1; total=14
17.000: read: 1; total=15
18.000: read: 1; total=16
19.000: read: 1; total=17
20.000: read: 1; total=18
21.000: read: 1; total=19
22.000: read: 1; total=20
23.000: read: 1; total=21
24.000: read: 1; total=22
25.000: read: 1; total=23
26.000: read: 1; total=24
27.000: read: 1; total=25
28.000: read: 1; total=26
29.000: read: 1; total=27
30.000: read: 1; total=28
31.000: read: 1; total=29
33.330: read: 489; total=518 # wake up here
tian@tian-B250M-Wind:~/playground/libuv-vs-libevent$ 
      tian@tian-B250M-Wind:~/Desktop/playground/libuv-vs-libevent$ ./timefd_create_monotonic_clock 3 1 100
0.000: timer started
3.000: read: 1; total=1
3.1000: read: 1; total=2
4.1000: read: 1; total=3
6.000: read: 1; total=4
7.000: read: 1; total=5
7.1000: read: 1; total=6
9.000: read: 1; total=7
10.000: read: 1; total=8
11.000: read: 1; total=9
12.000: read: 1; total=10
13.000: read: 1; total=11
14.000: read: 1; total=12
15.000: read: 1; total=13
16.000: read: 1; total=14
16.1000: read: 1; total=15
18.000: read: 1; total=16
19.000: read: 1; total=17
19.1000: read: 1; total=18
21.000: read: 1; total=19
22.001: read: 1; total=20
23.000: read: 1; total=21
25.482: read: 2; total=23
26.000: read: 1; total=24
26.1000: read: 1; total=25
28.000: read: 1; total=26
28.1000: read: 1; total=27
29.1000: read: 1; total=28
30.1000: read: 1; total=29
31.1000: read: 1; total=30
32.1000: read: 1; total=31
33.1000: read: 1; total=32
35.000: read: 1; total=33
36.000: read: 1; total=34
36.1000: read: 1; total=35
38.000: read: 1; total=36
39.000: read: 1; total=37
40.000: read: 1; total=38
40.1000: read: 1; total=39
42.000: read: 1; total=40
43.001: read: 1; total=41
43.1000: read: 1; total=42
45.000: read: 1; total=43
46.000: read: 1; total=44
47.000: read: 1; total=45
47.1000: read: 1; total=46
48.1000: read: 1; total=47
50.001: read: 1; total=48
^C
tian@tian-B250M-Wind:~/Desktop/playground/libuv-vs-libevent$

CLOCK_REALTIME: Абсолютное время (например, 01. 07.2020)

CLOCK_MONOTONIC: Относительное время (например, через 5 секунд или 10 минут назад)

Другие вопросы по тегам