Разница между 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);
}
скомпилируйте оба и запустите в 2 вкладках в одном терминале
./timefd_create_monotonic_clock 3 1 100
./timefd_create_realtime_clock 3 1 100
перевести мой рабочий стол Ubuntu в спящий режим
Подождите несколько минут
Разбудите мою Ubuntu, нажав кнопку питания один раз
Проверьте вывод терминала
Выход:
Часы реального времени тут же остановились. Потому что он подсчитал время, прошедшее, когда компьютер был приостановлен / спит.
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 минут назад)