如何查看系统是否支持"Monotonic Clock"?

How to check if the system supports "Monotonic Clock"?

我需要在代码中处理超时情况,如果系统支持单调时钟,我想使用clock_gettime(CLOCK_MONOTONIC)

#ifdef CLOCK_MONOTONIC
    clock_gettime(CLOCK_MONOTONIC, & spec);
#else
    clock_gettime(CLOCK_REALTIME,  & spec);
#endif

我不确定这是否足够。也就是说,有没有可能是系统定义的CLOCK_MONOTONIC并不真正支持monotonic clock?或者检查是否支持单调时钟的可靠方法是什么?

POSIX只需要CLOCK_REALTIME存在,其他时钟可选。

如果单调时钟可用,宏_POSIX_MONOTONIC_CLOCK将在unistd.h中定义(根据[=13=的可用性部分])

根据 POSIX 的字母,即使定义了常量 CLOCK_MONOTONIC,您实际上也可能需要运行时测试。处理这个问题的官方方法是使用 _POSIX_MONOTONIC_CLOCK "feature-test macro",但这些宏的语义非常复杂:引用 http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/unistd.h.html

If a symbolic constant is not defined or is defined with the value -1, the option is not supported for compilation. If it is defined with a value greater than zero, the option shall always be supported when the application is executed. If it is defined with the value zero, the option shall be supported for compilation and might or might not be supported at runtime.

将这种三向区别转化为代码会给你这样的东西:

#if !defined _POSIX_MONOTONIC_CLOCK || _POSIX_MONOTONIC_CLOCK < 0
    clock_gettime(CLOCK_REALTIME, &spec);
#elif _POSIX_MONOTONIC_CLOCK > 0
    clock_gettime(CLOCK_MONOTONIC, &spec);
#else
    if (clock_gettime(CLOCK_MONOTONIC, &spec))
        clock_gettime(CLOCK_REALTIME, &spec));
#endif

但是,如果您总是在定义 CLOCK_MONOTONIC 本身时进行运行时测试,它会更简单且更易读:

#ifdef CLOCK_MONOTONIC
    if (clock_gettime(CLOCK_MONOTONIC, &spec))
#endif
        clock_gettime(CLOCK_REALTIME, &spec);

这在支持 CLOCK_MONOTONIC 的当前一代 OSes 上增加了一些微不足道的代码量,但在我看来,可读性的好处是值得的。

无条件使用 CLOCK_MONOTONIC 也有很强的论据;与 OS 有 clock_gettime 但没有 CLOCK_MONOTONIC.

如@zwol 的回答所示,事情确实变得有点复杂和有趣 --

看下面的简单程序(foo.c):

#include <stdio.h>
#include <unistd.h>
#include <time.h>

int
main()
{
    struct timespec spec;

    printf("_POSIX_MONOTONIC_CLOCK         = %d\n",
           (int)_POSIX_MONOTONIC_CLOCK);
    printf("sysconf(_SC_MONOTONIC_CLOCK)   = %ld\n",
           sysconf(_SC_MONOTONIC_CLOCK) );
    printf("clock_gettime(CLOCK_MONOTONIC) = %d\n",
           clock_gettime(CLOCK_MONOTONIC, & spec) );

    return 0;
}

在 Linux(Debian 9,x86_64)上:

[STEP 101] # uname -a
Linux debian9 4.9.0-6-amd64 #1 SMP Debian 4.9.88-1 (2018-04-29) x86_64 GNU/Linux
[STEP 102] # gcc foo.c && ./a.out
_POSIX_MONOTONIC_CLOCK         = 0
sysconf(_SC_MONOTONIC_CLOCK)   = 200809
clock_gettime(CLOCK_MONOTONIC) = 0
[STEP 103] #

在 macOS(10.13,High Sierra)上:

[STEP 201] $ uname -a
Darwin macbook.home 17.5.0 Darwin Kernel Version 17.5.0: Fri Apr 13 19:32:32 PDT 2018; root:xnu-4570.51.2~1/RELEASE_X86_64 x86_64
[STEP 202] $ cc foo.c && ./a.out
_POSIX_MONOTONIC_CLOCK         = -1
sysconf(_SC_MONOTONIC_CLOCK)   = -1
clock_gettime(CLOCK_MONOTONIC) = 0
[STEP 203] $

在 FreeBSD(11.1,x86_64)上:

[STEP 301] # uname -a
FreeBSD freebsd 11.1-RELEASE FreeBSD 11.1-RELEASE #0 r321309: Fri Jul 21 02:08:28 UTC 2017     root@releng2.nyi.freebsd.org:/usr/obj/usr/src/sys/GENERIC  amd64
[STEP 302] # cc foo.c && ./a.out 
_POSIX_MONOTONIC_CLOCK         = 200112
sysconf(_SC_MONOTONIC_CLOCK)   = 200112
clock_gettime(CLOCK_MONOTONIC) = 0
[STEP 303] #

macOS 上的结果真的让我很惊讶。 sysconf() returns -1 但是 clock_gettime(CLOCK_MONOTONIC) 成功了!不确定这是否表明 macOS 不兼容 POSIX。无论如何,它证明 使用 sysconf() 进行运行时检查是不可靠的!

最后我要这样做:

int
Clock_gettime(struct timespec * spec)
{
    static bool firstime = true;
    static clockid_t clock = CLOCK_REALTIME;

    if (firstime) {
        firstime = false;
#ifdef CLOCK_MONOTONIC
        if (clock_gettime(CLOCK_MONOTONIC, spec) == 0) {
            clock = CLOCK_MONOTONIC;
            return 0;
        }
#endif
    }

    return clock_gettime(clock, spec);
}