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

Some hack to set the monotonic clock in C++ / Linux

本文关键字:时钟 单调 C++ Linux 设置      更新时间:2023-10-16

Linux C++问题,他们说你不能设置单调时钟。我想设置单调时钟。Linux中的C++有什么破解或技巧可以做到这一点吗?我想使用clock_settime(clock_MONOTONC,&ts(并让它将时钟设置为ts中指定的时间,或者使用clock.settime以外的其他方法来完成同样的事情?我对精心设计的技巧或变通方法很冷静。

  1. 阅读
  2. 从您希望的值中减去当前值
  3. 将来,添加此偏移

clock_gettime()clock_settime()与自己的插入。

这是一个经过测试的工作示例,应该是线程安全的。首先,libmonoton.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的初始偏移。

对于测试(获取单调时钟(,这里有一个简单的测试程序示例,monoton.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.%09ldn", (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.%09ldn", (long)now.tv_sec, now.tv_nsec);
return EXIT_SUCCESS;
}

请注意,此程序没有以任何方式与libmononic.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差异:这是加载libmonetic.so时LIBMONOTONIC_OFFSET环境变量的影响。

此外,当加载libmonetic.so时,clock_settime((调用成功。

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

检查std::chrono::steady_clock