linux CLOCK_REALTIME和CLOCK_MONOTONIC之间的差异?

anauzrmj  于 2022-11-22  发布在  Linux
关注(0)|答案(8)|浏览(144)

您能解释一下Linux上clock_gettime()返回的CLOCK_REALTIMECLOCK_MONOTONIC时钟之间的区别吗?
如果我需要计算外部源产生的时间戳与当前时间之间的经过时间,哪一个是更好的选择?
最后,如果我有一个NTP守护进程定期调整系统时间,这些调整如何与CLOCK_REALTIMECLOCK_MONOTONIC交互?

zqry0prt

zqry0prt1#

CLOCK_REALTIME表示机器对当前挂钟时间的最佳猜测,正如伊格纳西奥和MarkR所说,这意味着CLOCK_REALTIME可以随着系统时钟的变化(包括NTP)向前和向后跳转。
CLOCK_MONOTONIC表示从过去某个任意固定点开始的绝对已用挂钟时间。它不受系统时钟时间变化的影响。
如果要计算在一台计算机上观察到的两个事件之间的运行时间,而不需要重新引导,则CLOCK_MONOTONIC是最佳选项。
注意,在Linux上,CLOCK_MONOTONIC不测量挂起所花费的时间,尽管POSIX定义应该这样做。您可以使用Linux特定的CLOCK_BOOTTIME作为在挂起期间保持运行的单调时钟。

bttbmeg0

bttbmeg02#

Robert Love的书LINUX System Programming 2nd Edition在第11章第363页的开头专门回答了您的问题:
单调时间源的重要方面不是当前值,而是时间源严格线性增加的保证,因此对于计算两个采样之间的时间差非常有用
也就是说,我相信他是假设进程运行在同一个操作系统示例上,所以您可能希望有一个定期校准运行,以便能够估计漂移。

tuwxkamq

tuwxkamq3#

CLOCK_REALTIME受NTP影响,可以向前和向后移动。CLOCK_MONOTONIC不受NTP影响,以每一个时钟周期前进一个时钟周期。

flvtvl50

flvtvl504#

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内核实现似乎将 Boot 时间作为CLOCK_MONOTONIC的纪元:Starting point for CLOCK_MONOTONIC

muk1a3rh

muk1a3rh5#

除了Ignacio's answer之外,CLOCK_REALTIME可以向前跳跃,偶尔也可以向后跳跃。它只是继续前进(尽管它可能在重新启动时重置)。
一个健壮的应用程序需要能够容忍CLOCK_REALTIME偶尔向前跳跃(也许偶尔稍微向后跳跃,尽管这更多的是一种边缘情况)。
想象一下当你暂停笔记本电脑时会发生什么-CLOCK_REALTIME在恢复后向前跳,CLOCK_MONOTONIC没有。

brvekthn

brvekthn6#

对不起,没有声誉添加这作为一个评论。所以它作为一个补充的答案去。
根据您调用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

9gm1akwq

9gm1akwq7#

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

brjng4g3

brjng4g38#

我想澄清一下,在这种情况下,“系统暂停”是什么意思。
我正在阅读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计算计算机处于睡眠状态的时间。
比较这两段代码的输出

创建实时时钟. 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);
}
创建单调时钟. 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
1.让我的Ubuntu桌面进入睡眠状态
1.等几分钟
1.按一次电源按钮唤醒我的Ubuntu
1.检查终端输出
输出量:
实时时钟立即停止。因为它已经计算了计算机挂起/睡眠的时间。
第一个

相关问题