在 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 中设置单调时钟的一些技巧的主要内容,如果未能解决你的问题,请参考以下文章
我正在尝试 JNI 如何在 Linux 中设置 java.library.path