有没有办法在 Linux 上获取正确的 CLOCK_TAI?

有没有办法在 Linux 上获取正确的 CLOCK_TAI?

我的 Linux 机器支持CLOCK_TAI,但其偏移量CLOCK_REALTIME错误地为零(这是默认值)。是否有软件或其他解决方案可以维持CLOCK_TAITAI?

来自答案这里这里,似乎ntpd也没有chronyd这样做。

我很乐意将我的硬件时钟设置为 TAI 来实现这一点,CLOCK_REALTIME并提供gettimeofday等返回 POSIX 时间(基本上是 UTC)。

答案1

我想您希望clock_gettime能够CLOCK_TAI正常工作。我也是。

参考答案中的关键句子是:“请注意,CLOCK_REALTIME 的偏移量在启动时初始化为零,并且 ntpd 和 chronyd 都没有默认将其设置为正确值(当前为 35)。”

除了偏移量现在是 37 之外,这可能仍然是正确的,但至少可以配置最近的 ntpd 来设置偏移量。我在 openSUSE 机器上执行以下操作:

vi /etc/ntp.conf # Add the line: leapfile /var/lib/ntp/etc/ntp.leapseconds
update-leap
service ntpd restart
less /var/log/ntp # Check for errors

然后clock_gettime(CLOCK_TAI, &res)似乎工作正常。

我认为 ntp 使用ntp_adjtime来设置偏移量MOD_TAI。使用 搜索 chrony 源grep -P '(ADJ|MOD)_TAI'未找到任何匹配项,因此看来 chrony 尚不具备此功能。

答案2

您可以libtai从 djb 使用:https://cr.yp.to/libtai.html

它是什么?

libtai 是一个用于存储和操作日期和时间的库。

libtai 支持两种时间尺度:(1) TAI64,覆盖数千亿年,精度为 1 秒;(2) TAI64NA,覆盖同一时期,精度为 1 阿秒。两种尺度均根据当前国际实时标准 TAI 定义。

libtai 为 TAI64 提供了一种内部格式,即 struct tai,专为快速时间操作而设计。tai_pack() 和 tai_unpack() 例程可在 struct tai 和可移植的 8 字节 TAI64 存储格式之间进行转换。libtai 为 TAI64NA 提供了类似的内部和外部格式。

libtai 提供 struct caldate 以年月日形式存储日期。它可以将公历下的 struct caldate 转换为修正的儒略日数,以方便进行日期运算。

libtai 提供 struct caltime 来存储日历日期和时间以及 UTC 偏移量。它可以将 struct tai 转换为 UTC 中的 struct caltime,考虑到闰秒,以准确显示日期和时间。它还可以将 struct caltime 转换回 struct tai 以供用户输入。其整体 UTC 到 TAI 的转换速度比通常的 UNIX mktime() 实现快 100 倍。

此版本的 libtai 需要具有 gettimeofday() 的 UNIX 系统。使用支持 64 位算法的编译器可以轻松移植到其他操作系统。

libtai 源代码属于公共领域。

答案3

由于我正在运行chrony而不是旧的ntpd,我没有自动化的方法来正确获取内核参数,所以我研究了另一种方法。

由于 TAI 和 UTC 之间的偏移相对恒定(每年变化一次),因此可以静态设置内核参数,然后在应用程序中使用 CLOCK_TAI 时钟将给出正确的值。

在 中有一个用于在内核源代码中设置内核偏移的测试应用程序tools/testing/selftests/timers/set-tai.c。并且,假设您已tzdata安装该软件包,则 中有一个文件,其中包含 UTC 和 TAI 之间的偏移量/usr/share/zoneinfo/leap-seconds.list

我砍掉了内核测试应用程序,因此主要内容如下:

int main(int argc, char **argv)
{
    int i, ret;

    ret = get_tai();
    printf("tai offset started at %i\n", ret);

    if (argc < 2)
    {
        printf("New offset not given, not setting\n");
    }
    else
    {
        i = strtol(argv[1],NULL,10);
        printf("Attempting to set TAI offset to %d\n",i);
        printf("Checking tai offsets can be properly set: ");
        ret = set_tai(i);
        ret = get_tai();
        if (ret != i) {
            printf("[FAILED] expected: %i got %i\n", i, ret);
            return EXIT_FAILURE;
        }
    }
    printf("[OK]\n");
    return EXIT_SUCCESS;
}

然后,对于我的用例,只需从文件中提取正确的值并将其作为参数leap-seconds.list运行(以使其在启动时发生)。执行此操作的一个示例方法是:set-tai/etc/rc.local

TAI_OFFSET=$(grep -v '^#' /usr/share/zoneinfo/leap-seconds.list | tail -1 | awk '{ print $2 }')
if [ -x /usr/local/sbin/set-tai ]; then
  /usr/local/sbin/set-tai $TAI_OFFSET
fi

请注意,要使上述代码片段正常工作,leap-seconds.list 文件假定在最后一个非注释类型行中具有当前有效的 TAI_OFFSET。如果文件中有更多当前行,或者存在除当前日期偏移以外的问题,则需要使用一些更高级的解析方法。

希望这对其他人有用!

相关内容