CLOCK_REALTIME和CLOCK_MONOTONIC的区别?

你能解释一下Linux上由clock_gettime()返回的CLOCK_REALTIMECLOCK_MONOTONIC时钟之间的区别吗?

如果我需要计算外部源产生的时间戳与当前时间之间的经过时间,那么哪个是更好的选择?

最后,如果我有一个NTP守护进程定期调整系统时间,这些调整如何与CLOCK_REALTIMECLOCK_MONOTONIC相互作用?

230603 次浏览

CLOCK_REALTIME受NTP影响,可以向前和向后移动。CLOCK_MONOTONIC不是,它每滴答滴答前进一次。

除了Ignacio的回答之外,CLOCK_REALTIME可以跳跃式地向前,偶尔也可以向后。CLOCK_MONOTONIC两者都没有;它只是继续前进(尽管它可能在重新启动时重置)。

一个健壮的应用程序需要能够容忍CLOCK_REALTIME偶尔向前跳跃(也许向后非常轻微,非常偶尔,尽管这是一个边缘情况)。

想象一下,当你暂停笔记本电脑时会发生什么——CLOCK_REALTIME在简历后面跳转,而CLOCK_MONOTONIC没有。在虚拟机上试试。

CLOCK_REALTIME表示机器对当前挂钟时间的最佳猜测。正如IgnacioMarkR所说,这意味着CLOCK_REALTIME可以随着系统时间时钟的改变向前和向后跳转,包括通过NTP。

CLOCK_MONOTONIC表示从过去某个任意固定点开始经过的绝对时钟时间。它不受系统时间时钟变化的影响。

如果你想计算在一台机器上观察到的两个事件之间的时间,而不需要重新启动,CLOCK_MONOTONIC是最好的选择。

注意,在Linux上,CLOCK_MONOTONIC不测量挂起花费的时间,尽管根据POSIX定义它应该测量。你可以使用linux特有的CLOCK_BOOTTIME作为一个单调时钟,在挂起期间保持运行。

Robert Love的书LINUX系统编程第二版,在第11章第363页的开头特别回答了你的问题:

单调时间源的重要方面不是电流 值,但保证时间源是严格线性的 递增的,因此对计算时间差很有用 在两个采样

也就是说,我相信他假设进程运行在一个操作系统的同一个实例上,所以您可能需要定期运行校准,以便能够估计漂移。

POSIX 7引号

POSIX 7在http://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html指定两者:

CLOCK_REALTIME:

该时钟表示测量系统实时时间的时钟。对于这个时钟,clock_gettime()返回的值和clock_settime()指定的值表示自Epoch以来的时间量(以秒和纳秒为单位)。

CLOCK_MONOTONIC(可选特性):

对于这个时钟,clock_gettime()返回的值表示从过去某个未指定的时间点(例如,系统启动时间或Epoch)开始的时间量(以秒和纳秒为单位)。这一点在系统启动后不会改变。CLOCK_MONOTONIC时钟的值不能通过clock_settime()来设置。

clock_settime()给出了一个重要的提示:POSIX系统能够任意地改变CLOCK_REALITME,所以不要依赖于它既不连续也不向前流动。NTP可以使用clock_settime()实现,并且只能影响CLOCK_REALTIME

Linux内核实现似乎将引导时间作为CLOCK_MONOTONIC: CLOCK_MONOTONIC的起点的纪元

不好意思,没有声望可以添加这条评论。所以这是一个互补答案。

取决于你调用clock_gettime()的频率,你应该记住只有一些的&;clock &;由Linux在VDSO中提供(即不需要系统调用的所有开销—当Linux添加防御来防止类似幽灵的攻击时,这只会变得更糟)。

虽然clock_gettime(CLOCK_MONOTONIC,...)clock_gettime(CLOCK_REALTIME,...)gettimeofday()总是非常快(由VDSO加速),但对于clock_单调ic_raw或任何其他POSIX时钟,这是

这可以随着内核版本和体系结构的变化而改变。

尽管大多数程序不需要注意这一点,但是VDSO加速的时钟可能会出现延迟峰值:如果您在内核用时钟计数器更新共享内存区域时恰好击中它们,那么它必须等待内核完成。

这是“证据”;(GitHub,让机器人远离kernel.org): https://github.com/torvalds/linux/commit/2aae950b21e4bc789d1fc6668faf67e8748300b7 < / p >

CLOCK_REALTIME和MONOTONIC之间有一个很大的区别。CLOCK_REALTIME可以根据NTP向前跳或向后跳。 缺省情况下,NTP允许时钟速率的加速或减速不超过0.05%,但是NTP不能使单调时钟向前或向后跳变

我想澄清一下什么“系统被暂停”。意思是在这种情况下。

我正在阅读timefd_create和从manpage, https://man7.org/linux/man-pages/man2/timerfd_create.2.html < / p >

   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_REALTIMECLOCK_BOOTTIME仍然计算时间,而CLOCK_MONOTONIC则不计算时间。

我对“系统被暂停”感到困惑;意思是没错。起初,我认为这意味着当我们从终端发送Ctrl + Z时,使进程挂起。但事实并非如此。

@MarkR的回答启发了我:

想象一下当你暂停你的笔记本电脑 - ....会发生什么< >强试一试 .

. on a VM

所以从字面上看“制度被中止”;意思是你把电脑调到睡眠模式了。

enter image description here

也就是说,CLOCK_REALTIME计算计算机处于睡眠状态的时间。


比较这两段代码的输出

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 < / p >

  2. < p > 让我的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$