CLOCK_REALTIME 和 CLOCK_MONOTONIC 之间的区别?

2024-10-09 09:11:00
admin
原创
86
摘要:问题描述:您能解释一下Linux返回的时钟CLOCK_REALTIME之间的区别吗?CLOCK_MONOTONIC`clock_gettime()`如果我需要计算外部源产生的时间戳和当前时间之间的经过时间,哪个是更好的选择?最后,如果我有一个 NTP 守护进程定期调整系统时间,这些调整如何与CLOCK_REA...

问题描述:

您能解释一下Linux返回的时钟CLOCK_REALTIME之间的区别吗?CLOCK_MONOTONIC`clock_gettime()`

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

最后,如果我有一个 NTP 守护进程定期调整系统时间,这些调整如何与CLOCK_REALTIME和交互CLOCK_MONOTONIC


解决方案 1:

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

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

如果您想计算一台机器上观察到的两个事件之间的经过时间而无需中间重新启动,CLOCK_MONOTONIC这是最好的选择。

请注意,在 Linux 上,CLOCK_MONOTONIC不会测量挂起所花费的时间,尽管根据 POSIX 定义应该如此。您可以使用 Linux 特定的CLOCK_BOOTTIME单调时钟,该时钟在挂起期间保持运行。

解决方案 2:

Robert Love 的书《 LINUX 系统编程第 2 版》在第 11 章第 363 页的开头专门回答了您的问题:

单调时间源的重要方面不是当前值,而是保证时间源严格线性增加,因此可用于计算两次采样之间的时间差

话虽如此,我相信他假设这些进程在同一个操作系统实例上运行,因此您可能需要进行定期校准以便能够估计漂移。

解决方案 3:

CLOCK_REALTIME受 NTP 影响,可以向前和向后移动。CLOCK_MONOTONIC不受影响,并且以每刻一刻的速度前进。

解决方案 4:

POSIX 7 引言

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_REALTIME

Linux 内核实现似乎将启动时间作为CLOCK_MONOTONIC 的CLOCK_MONOTONIC起点

解决方案 5:

除了Ignacio 的回答之外,CLOCK_REALTIME可以跳跃式地向前移动,偶尔也可以向后移动。CLOCK_MONOTONIC两者都不做;它只是一直向前移动(尽管它可能会在重启时重置)。

一个强大的应用程序需要能够容忍CLOCK_REALTIME偶尔的向前跳跃(也许偶尔会稍微向后跳跃,尽管这更像是一种边缘情况)。

想象一下当您暂停笔记本电脑时会发生什么 -CLOCK_REALTIME恢复后向前跳转,CLOCK_MONOTONIC但不会。在虚拟机上尝试一下。

解决方案 6:

抱歉,没有信誉可将此添加为评论。因此,它作为补充答案。

根据您调用的频率clock_gettime(),您应该记住,VDSO 中的 Linux 仅提供了部分“时钟”(即,不需要具有所有开销的系统调用 - 当 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

解决方案 7:

CLOCK_REALTIME 和 MONOTONIC 之间有一个很大的区别。CLOCK_REALTIME 可以根据 NTP 向前或向后跳转。默认情况下,NTP 允许时钟速率加快或减慢最多 0.05%,但 NTP 不能使单调时钟向前或向后跳转。

解决方案 8:

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

我正在阅读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_REALTIMECLOCK_BOOTTIME系统挂起时, 仍然计时, 而CLOCK_MONOTONIC则不计时。

我对“系统已暂停”的确切含义感到困惑。起初我以为这意味着当我们Ctrl + Z从终端发送消息时,进程被暂停。但事实并非如此。

@MarkR 的回答启发了我:

想象一下当您暂停笔记本电脑时会发生什么- ....在虚拟机上尝试一下

因此,字面意思是“系统暂停”意味着您将计算机置于睡眠模式。

在此处输入图片描述

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


比较这两段代码的输出

timefd_创建_实时时钟.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) \n        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]
",
                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
");

    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 "
", 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) \n        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]
",
                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
");

    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 "
", exp, tot_exp);
    }

    exit(EXIT_SUCCESS);
}
  1. 编译两者并在同一终端的两个选项卡中运行

./timefd_create_monotonic_clock 3 1 100

./timefd_create_realtime_clock 3 1 100

  1. 让我的 Ubuntu 桌面进入睡眠状态

  2. 等待几分钟

  3. 按一次电源按钮唤醒我的 Ubuntu

  4. 检查终端输出

输出:

实时时钟立即停止。因为它计算了计算机挂起/睡眠时经过的时间。

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$
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用