在 C++/Linux 中设置单调时钟的一些技巧

Posted

技术标签:

【中文标题】在 C++/Linux 中设置单调时钟的一些技巧【英文标题】:Some hack to set the monotonic clock in C++ / Linux 【发布时间】:2020-07-16 04:48:04 【问题描述】:

Linux C++ 问题,他们说你不能设置单调时钟。我想设置单调时钟。在 Linux 中从 C++ 执行此操作是否有一些技巧或技巧?我想使用clock_settime(CLOCK_MONOTONC, &ts) 并将时钟设置为ts 中指定的时间,或者使用clock_settime 以外的其他方法来完成同样的事情?我对精心设计的技巧或解决方法很感兴趣。

【问题讨论】:

"CLOCK_MONOTONIC: A nonsettable 系统范围的时钟..... CLOCK_MONOTONIC 时钟不受系统时间不连续跳转的影响,但会受到执行的增量调整的影响by adjtime(3) ..." 链接:man7.org/linux/man-pages/man2/clock_settime.2.html 和 man7.org/linux/man-pages/man3/adjtime.3.html 可设置的时钟将不再是单调的,并且会破坏所有依赖时钟的单调性保证的软件。 这是XY problem吗?您要解决的实际问题是什么导致了这个问题? 【参考方案1】:
    阅读。 从您希望的值中减去当前值。 以后添加此偏移量。

【讨论】:

【参考方案2】:

用你自己的插入 clock_gettime()clock_settime()

这是一个经过测试的工作示例,它应该是线程安全的。首先,libmonotonic.c 本身:

#define  _POSIX_C_SOURCE  200809L
#define  _GNU_SOURCE
#include <stdlib.h>
#include <dlfcn.h>
#include <time.h>
#include <errno.h>

#ifndef  ENV_OFFSET_NAME
#define  ENV_OFFSET_NAME  "LIBMONOTONIC_OFFSET"
#endif

typedef int (*clock_gettime_funcptr)(clockid_t, struct timespec *);
typedef int (*clock_settime_funcptr)(clockid_t, const struct timespec *);

static volatile clock_gettime_funcptr  original_clock_gettime = NULL;
static volatile clock_settime_funcptr  original_clock_settime = NULL;

static volatile long  offset_sec  = 0;
static volatile long  offset_nsec = 0;

/* Call original clock_gettime(). */
static inline int do_clock_gettime(clockid_t clk_id, struct timespec *tp)

    clock_gettime_funcptr  original = __atomic_load_n(&original_clock_gettime, __ATOMIC_SEQ_CST);
    if (!original) 
        original = dlsym(RTLD_NEXT, "clock_gettime");
        if (!original) 
            errno = EINVAL;
            return -1;
        
    
    return original(clk_id, tp);


/* Call original clock_settime(). */
static inline int do_clock_settime(clockid_t clk_id, const struct timespec *tp)

    clock_settime_funcptr  original = __atomic_load_n(&original_clock_settime, __ATOMIC_SEQ_CST);
    if (!original) 
        original = dlsym(RTLD_NEXT, "clock_settime");
        if (!original) 
            errno = EINVAL;
            return -1;
        
    
    return original(clk_id, tp);


/* Interposed version of clock_settime(). */
int clock_settime(clockid_t clk_id, const struct timespec *tp)

    if (clk_id != CLOCK_MONOTONIC)
        return do_clock_settime(clk_id, tp);

    struct timespec  now;

    if (do_clock_gettime(CLOCK_MONOTONIC, &now) == -1)
        return -1;

    long  off_sec  = tp->tv_sec  - now.tv_sec;
    long  off_nsec = tp->tv_nsec - now.tv_nsec;

    if (off_nsec <= -1000000000) 
        off_sec -=   (-off_nsec) / 1000000000;
        off_nsec = -((-off_nsec) % 1000000000);
    
    if (off_nsec >= 1000000000) 
        off_sec += off_nsec / 1000000000;
        off_nsec = off_nsec % 1000000000;
    

    do 
        __atomic_store_n(&offset_sec,  off_sec,  __ATOMIC_SEQ_CST);
        __atomic_store_n(&offset_nsec, off_nsec, __ATOMIC_SEQ_CST);
     while (offset_sec != off_sec || offset_nsec != off_nsec);

    return 0;


int clock_gettime(clockid_t clk_id, struct timespec *tp)

    if (clk_id != CLOCK_MONOTONIC)
        return do_clock_gettime(clk_id, tp);

    struct timespec  now;
    long             off_sec, off_nsec;

    if (do_clock_gettime(clk_id, &now) == -1)
        return -1;

    do 
        off_sec  = __atomic_load_n(&offset_sec,  __ATOMIC_SEQ_CST);
        off_nsec = __atomic_load_n(&offset_nsec, __ATOMIC_SEQ_CST);
     while (off_sec != offset_sec || off_nsec != offset_nsec);

    now.tv_sec  += off_sec;
    now.tv_nsec += off_nsec;

    if (now.tv_nsec < 0) 
        const long  nsec = -now.tv_nsec;
        now.tv_sec -= 1 + nsec / 1000000000;
        now.tv_nsec = 1000000000 - (nsec % 1000000000);
    
    if (now.tv_nsec >= 1000000000) 
        now.tv_sec += now.tv_nsec / 1000000000;
        now.tv_nsec = now.tv_nsec % 1000000000;
    

    *tp = now;
    return 0;


static void monotonic_init(void) __attribute__((constructor));
static void monotonic_init(void)

    const int   saved_errno = errno;
    const char *value = getenv(ENV_OFFSET_NAME);

    if (value && *value)
        do 
            long  off_sec = 0, off_nsec = 0;
            long  decimal = 100000000;
            int   negative = 0;

            /* Skip leading whitespace. */
            while (*value == '\t' || *value == '\n' || *value == '\v' ||
                   *value == '\f' || *value == '\r' || *value == ' ')
                value++;

            /* Parse signs. */
            while (*value == '+' || *value == '-')
                if (*(value++) == '-')
                    negative = !negative;

            /* Require first digit. */
            if (*value >= '0' && *value <= '9')
                off_sec = (*(value++) - '0');
            else
                break;

            /* Parse the rest of the integer digits. */
            while (*value >= '0' && *value <= '9')
                off_sec = (10 * off_sec) + (*(value++) - '0');

            /* Fractional part? */
            if (*value == '.') 
                value++;

                while (*value >= '0' && *value <= '9') 
                    if (decimal > 0) 
                        off_nsec += decimal * (long)((*value) - '0');
                        decimal /= 10;
                    
                    value++;
                
            

            /* Skip trailing whitespace. */
            while (*value == '\t' || *value == '\n' || *value == '\v' ||
                   *value == '\f' || *value == '\r' || *value == ' ')
                value++;

            /* Garbage? */
            if (*value)
                break;

            /* Negative? */
            if (negative) 
                off_sec  = -off_sec;
                off_nsec = -off_nsec;
            

            /* Store. */
            do 
                __atomic_store_n(&offset_sec,  off_sec,  __ATOMIC_SEQ_CST);
                __atomic_store_n(&offset_nsec, off_nsec, __ATOMIC_SEQ_CST);
             while (offset_sec != off_sec || offset_nsec != off_nsec);

        while (0);

    errno = saved_errno;

每当加载库时,都会执行monotonic_init() 函数。 (如果在LD_PRELOAD中使用,则在main()之前执行。)如果设置了LIBMONOTONIC_OFFSET,则将其用作CLOCK_MONOTONIC的初始偏移量。

对于测试(获取单调时钟),这是一个简单的示例测试程序,monotonic.c

#define _POSIX_C_SOURCE  200809L
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>

int main(void)

    struct timespec now;

    if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) 
        fprintf(stderr, "Cannot get monotonic clock: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    
    printf("%ld.%09ld\n", (long)now.tv_sec, now.tv_nsec);

    /* Set clock to zero. */
    now.tv_sec = 0;
    now.tv_nsec = 0;
    if (clock_settime(CLOCK_MONOTONIC, &now) == -1) 
        fprintf(stderr, "Cannot set monotonic clock: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    
    printf("Monotonic clock was set to %ld.%09ld successfully.\n", (long)now.tv_sec, now.tv_nsec);

    if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) 
        fprintf(stderr, "Cannot get monotonic clock: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    
    printf("%ld.%09ld\n", (long)now.tv_sec, now.tv_nsec);

    return EXIT_SUCCESS;

请注意,此程序与 libmonotonic.so 没有任何关联。

最后,这里是 Makefile 来帮助编译所有这些:

CC      := gcc
CFLAGS  := -Wall -Wextra -O2
LDFLAGS := -ldl
TARGETS := libmonotonic.so monotonic

all: $(TARGETS)

clean:
    rm -f *.o $(TARGETS)

%.o: %.c
    $(CC) $(CFLAGS) -c $^

%.so: %.o
    $(CC) $(CFLAGS) -shared -Wl,-soname,$@ $^ $(LDFLAGS) -o $@

monotonic: monotonic.o
    $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@

注意,因为这个论坛吃Tabs,你需要在创建后修复Makefile的缩进,通过运行sed -e 's|^ *|\t|' -i Makefile

运行make clean all 来构建库和示例程序。然后,运行例如

./monotonic
env LD_PRELOAD=$PWD/libmonotonic.so LIBMONOTONIC_OFFSET=-5321.2345 ./monotonic
./monotonic

在我的系统上,输出恰好是

1139233.519719376
Cannot set monotonic clock: Invalid argument.

1133912.791484465
Monotonic clock was set to 0.000000000 successfully.
0.000007698

1139234.531043242
Cannot set monotonic clock: Invalid argument.

注意第一个和第二个数字之间的 -5321 差异:这是加载 libmonotonic.so 时 LIBMONOTONIC_OFFSET 环境变量的效果。

另外,当加载 libmonotonic.so 时,clock_settime() 调用成功。

请注意,如果二进制文件使用基于 CLOCK_MONOTONIC 的 TIMER_ABSTIME 计时器(通过timer_create()timer_settime() 等),还应该插入 timer_create()timer_delete() 以记住哪些计时器基于 CLOCK_MONOTONIC 和 timer_settime()在标志中设置 TIMER_ABSTIME 时调整初始时间。 (timer_gettime() 总是返回相对值,所以不需要插入;相对定时器也不需要任何调整。)

【讨论】:

【参考方案3】:

查看std::chrono::steady_clock

【讨论】:

以上是关于在 C++/Linux 中设置单调时钟的一些技巧的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Linux 中设置不到一秒钟的警报?

我正在尝试 JNI 如何在 Linux 中设置 java.library.path

怎么在linux环境变量中设置多个gcc头文件搜索路径?

Linux中设置vim自动在运算符号两边加上空格

在 linux 机器中设置时的 Sonarqube-4.0 问题 (linux-x86-32)

在 Yocto ext4 映像中设置 Linux 功能