【问题标题】:How to find out if the linux kernel will insert a leap second at the end of the month如何确定linux内核是否会在月底插入闰秒
【发布时间】:2014-11-29 21:58:51
【问题描述】:

假设我的程序在正确配置为处理leap seconds 的Linux 机器上运行。该配置是如何完成的(NTP,配置文件)应该与这个问题无关。

实际上,内核将在 UTC 月底插入额外的一秒或跳过一秒。这会对 gettimeofday(2) 读取的时间值产生影响。每月的最后一个 UTC 秒会重复或跳过。闰秒期间的时间读数示例为listed here

我的问题:在 C/C++ 程序中,我怎样才能知道闰秒是否会在月底发生,以及朝哪个方向。那么如何在linux上实现如下功能

int leap_seconds_scheduled_for_end_of_month() {
  if (/*kernel_will_insert_extra_second?*/)
    return 1;
  if (/*kernel_will_skip_over_last_second?*/)
    return -1;
  return 0;
}

如果月底很远,如果结果不正确也没关系。 (远,就我的目的而言,>=2 秒)答案必须在可能的闰秒之前的最后一秒内正确(*),即当月最后一天的 23:59:58 UTC。闰秒发生后才知道是不够的,还得提前做好准备。

我一直在尝试在 sysfs 或 procfs 中找到任何闰秒指标,但到目前为止都没有成功。

(*) 当然,如果内核本身仅在闰秒发生前一小部分知道闰秒,例如由于上个月整个 NTP 服务中断,所以当时的答案是不正确的。没关系。

【问题讨论】:

    标签: c linux time


    【解决方案1】:

    AFAIK,内核不会提前通知预定的闰秒。该知识由 NTP 守护进程维护,当需要修改系统时钟时,ntpd 发出 adjtimex(2) 系统调用,以调整系统时钟。

    戳你的 ntpd 守护进程:

    $ /usr/sbin/ntpq -c 'lassoc' -c "mrv &1 &999 leap,srcadr,stratum"
    
    ind assid status  conf reach auth condition  last_event cnt
    ===========================================================
      1  5159  80a3   yes    no  none    reject unreachable 10
      2  5160  968a   yes   yes  none  sys.peer    sys_peer  8
    srcadr=LOCAL(0), leap=00, stratum=10
    
    srcadr=timeserver.example.com, leap=00, stratum=4
    

    当您看到“01”设置为闰时,您的闰秒即将到来。 “srcaddr”行将针对您的 ntp 服务器配置的每个源重复,因此您可能有多个条目(我的示例返回虚拟本地系统 cl

    因此,对于 C++ 解决方案,您要么必须对 /usr/sbin/ntpq 进行丑陋的 fork/exec,然后捕获输出,要么挖掘 ntpq 用于与 NTP 守护进程对话的套接字协议,并且自己动手,从守护进程中获取响应。

    【讨论】:

    • 不相信... 这个内核邮件列表帖子linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/5246.html 似乎暗示 NTPd 几乎在得知闰秒后就告诉内核。并且闰秒标志在闰秒发生之前的整个月都在 ntp 中。回复帖子无助于使问题更清楚。我也发现 adjtimex 作为我想要查询的可能的内核接口。 timex 状态字段具有用于闰秒插入/删除的位。目前尚不清楚可以提前多长时间(如果有的话)查询。
    • 那么最后的答案就是直接去获取ntpd的源代码,筛选它,看看它做了什么。
    • 您的回答表明,最终启动时钟校正的不是内核,而是 NTP 守护进程。第三种可能性是它两者都不是,而是它是 glibc。棘手。即使我在 NTP 或 Kernel 或 glibc 中找到执行此操作的代码,我也无法 100% 确定我在正确的 #ifdef 分支中。如果我假设它是内核,那么我将如何测试它?您提出的解决方案很好,因为只要运行我的程序的系统上的时钟由 NTP 控制,它就可以在任何一种情况下工作。对我来说就是这种情况。将编辑您的答案或我的问题以包含功能
    • Hm 试图用 C 函数扩展这个答案,但被拒绝了。此处格式错误: int jump_seconds_scheduled_for_end_of_month() { char ntpq_output[1024];文件 * ntpq_stream = popen("/usr/bin/ntpq -c lassoc -c 'mrv &1 &999 跳跃,srcadr,stratum'","r"); memset(ntpq_output,0,1024); fread(ntpq_output,1,1022,ntpq_stream); pclose(ntpq_stream); char * jump_bits = strstr(ntpq_output,"leap=");如果 (leap_bits == NULL) 返回 0;跳跃位+= 5;如果 (leap_bits[0] == '0' && jump_bits[1] == '1') 返回 1;如果 (leap_bits[0] == '1' && jump_bits[1] == '0') 返回 -1;返回0; }
    • 我不知道 glibc 的任何部分实际上实现了完整的 NTP 协议以连接到 NTP 服务器、读取当前的闰秒标志并与内核通信。这简直是​​不可能的。 glibc 是一个库。它不是一个独立的进程,这种事情必须由一个独立的进程或内核来完成。
    【解决方案2】:

    内核保留一个标志,该标志定义它是否会在当前 (UTC) 日结束时添加(或删除)闰秒。如果支持这一点,则在距闰秒不到 1 天时由 ntpd 设置。

    所以你基本上可以通过检查这个标志来获得一天的闰秒警告,假设 (a) 你的 ntp 守护进程从它的源中得到通知,并且 (b) 你的 ntp 守护进程正确地确定你的内核支持这个。

    我不认为标志是通过 sysfs 或 procfs 暴露的;但是很容易通过 adjtimex 系统调用从 C 中检索:

    #include <sys/timex.h>
    #include <stdio.h>
    
    int main(int argc, char **argv)
    {
        struct timex buf;
        int res;
    
        buf.modes = 0;
    
        res = adjtimex(&buf);
        if(res < 0) {
            perror("Error calling adjtimex");
            return 1;
        }
    
        printf("clock status: %i\n", res);
        return 0;
    }
    

    如果闰秒即将到来,则结果为 1 或 2,由 adjtimex(2) 给出:

    成功时,adjtimex() 返回时钟状态:

    #define TIME_OK   0 /* clock synchronized */
    #define TIME_INS  1 /* insert leap second */
    #define TIME_DEL  2 /* delete leap second */
    #define TIME_OOP  3 /* leap second in progress */
    #define TIME_WAIT 4 /* leap second has occurred */
    #define TIME_BAD  5 /* clock not synchronized */
    

    【讨论】:

    • 感谢 C 解决方案。它目前指示我所有运行 NTP 的计算机上的正确更改。通过今晚即将到来的闰秒观察它是如何发展的。
    • 这在闰秒期间效果很好,但有一个不一致的地方。将我的读数发布在一个单独的答案中并接受这个。
    • 如果您有一个较旧的内核(几年前)并且已将ntpd 设置为具有-x 的转换模式,您可能会看到5,直到时钟转换完成。
    【解决方案3】:

    感谢 Sam Varshavchik 和 richvdh,他们都为这个问题提供了非常有用的答案。昨晚,发生了闰秒,现在我在多台计算机上使用他们的两种方法获得了示例读数。

    为了便于阅读,我将数据发布在单独的答案而不是评论中。以下 C 程序已用于读取时间并确定是否会发生闰秒:

    #include <stdio.h>
    #include <sys/time.h>
    #include <time.h>
    #include <string.h>
    #include <sys/timex.h>
    #include <unistd.h>
    
    /* This C function implements Sam Varshavchik's method */
    int leap_seconds_scheduled_for_end_of_month() {
      char ntpq_output[1024];
      /* The path to ntpq might have to be adjusted for your system */
      FILE * ntpq_stream = popen("/usr/bin/ntpq -c lassoc -c 'mrv &1 &999 leap,srcadr,stratum'","r");
      memset(ntpq_output,0,1024);
      fread(ntpq_output,1,1022,ntpq_stream);
      pclose(ntpq_stream);
      /* This finds the first leap=xx occurrence in ntpq's output. If 
         multiple upstream ntp servers are configured, there will be one
         line of text for each of them. It would be good to check that
         the stratum=xx value in the same line is not 16 (which means
         invalid). If it is, better use the leap=xx value from another 
         output line. Not done here for simplicity. */ 
      char * leap_bits = strstr(ntpq_output,"leap=");
      if (leap_bits == NULL)
        return 0;
      leap_bits += 5;
      if (leap_bits[0] == '0' && leap_bits[1] == '1')
        return 1;
      if (leap_bits[0] == '1' && leap_bits[1] == '0')
        return -1;
      return 0;
    }
    
    /* This function prints the following data in a single line of text:
       gettimeofday, clock_gettime for CLOCK_MONOTONIC, adjtimex (richvdh's
       solution), and Sam's solution */
    print_current_data() {
      struct timeval tv = {0,0};
      gettimeofday(&tv,NULL);
      printf("%u.%06u ", (unsigned)tv.tv_sec, (unsigned)tv.tv_usec);
    
      struct timespec ts = {0,0};
      clock_gettime(CLOCK_MONOTONIC, &ts);
      printf("%u.%09u ", (unsigned)ts.tv_sec, (unsigned)ts.tv_nsec);
    
      struct timex buf;
      buf.modes = 0;
      printf("%d ", adjtimex(&buf));
    
      printf("%2d\n", leap_seconds_scheduled_for_end_of_month());
    }
    
    /* main just calls print_current_data repeatedly. 
       If we are far away from the leap second, it sleeps between 
       printouts to reduce the ammount of data. */ 
    int main(int argc, char ** argv) {
      /* this is the time_t value for the second after the 2015 leap second */
      time_t post_leap = 1435708800;
      for(;;) {
        time_t now = time(NULL);
        print_current_data();
        if (now < (post_leap-60)) {
          sleep(30);
        } else if (now < (post_leap - 10)) {
          sleep(5);
        } else if (now < (post_leap - 2)) {
          usleep(500000);
        } else if (now <= (post_leap + 1)) {
        } else if (now > (post_leap + 120)) {
          return 0;
        } else if (now > (post_leap + 1)) {
          usleep(500000);
        }
      }
    
      return 0;
    }
    

    这里是昨晚的数据。为了不让你淹没在数据中,我只插入了发生有趣变化之前和之后的行,并用 [... skipping to xxx] 表示遗漏。

    每个数据行包含以下字段:
    gettimeofday, clock_gettime for CLOCK_MONOTONIC, adjtimex (richvdh's 解决方案)和 Sam 的解决方案(调用 ntpq 并解析输出)。

    系统 1:Cubieboard 运行 Linaro 13.04 和内核 3.0.62

    ntp 包版本为 1:4.2.6.p3+dfsg-1ubuntu5

    $ ./print_leap 
    1435703927.419452 7971483.087902293 1  1
    [... skipping to the second before the leap second]
    1435708798.992813 7976354.661146951 1  1
    1435708799.014143 7976354.682476687 1  1
    [... skipping to the leap second]
    1435708799.982292 7976355.650624344 1  1
    1435708799.007776 7976355.676111361 3  1
    [... skipping to the second after the leap second]
    1435708799.985337 7976356.653668890 3  1
    1435708800.007414 7976356.675746846 4  1
    [... skipping to a change in the adjtimex reading]
    1435708844.685062 7976401.353401529 4  1
    1435708845.204844 7976401.873191115 0  1
    [... skipping to the last programmatic reading]
    1435708921.124692 7976477.793033504 0  1
    

    在闰秒后约 4.5 小时的手动检查中,Sam 的方法也回到了 0。

    系统 2:使用内核 3.4.107-cubietruck 运行 Debian 8 的 Cubieboard 2

    ntp 包版本为 1:4.2.6.p5+dfsg-7

    $ ./print_leap
    1435703847.373559 948687.514617591 1  1
    [... skipping to the second before the leap second]
    1435708798.998980 953639.139771365 1  1
    1435708799.028375 953639.169165960 1  1
    [... skipping to the leap second]
    1435708799.986449 953640.127238940 1  1
    1435708799.020017 953640.160809364 3  1
    [... skipping to the second after the leap second]
    1435708799.984912 953641.125702281 3  1
    1435708800.012875 953641.153666113 4  1
    [... skipping to the last programmatic reading]
    1435708921.268660 953762.409472677 4  1
    $ ./print_leap  # manual reading several hours later
    1435725362.080253 970203.221195342 0  0
    

    系统 3:Raspberrypi 模型 B 运行带有内核 3.12.35+ 的 Raspbian 7

    ntp 包是版本 1:4.2.6.p5+dfsg-2+deb7u1

    $ ./print_leap 
    1435704085.032222 313606.064883453 1  1
    [... skipping to the second before the leap second]
    1435708798.969855 318320.002146274 1  1
    1435708799.040573 318320.072865002 1  1
    [... skipping to the leap second]
    1435708799.996887 318321.029180271 1  1
    1435708799.071282 318321.103573880 3  1
    [... skipping to the second after the leap second]
    1435708799.945609 318321.977898784 3  1
    1435708800.017116 318322.049407486 4  1
    [... skipping to the last programmatic reading]
    1435708921.136882 318443.169189210 4  1
    $ ./print_leap  # manual reading several hours later 
    1435732962.263988 342484.296617478 0  0
    

    系统 4:英特尔 NUC DN2820 运行 Ubuntu 15.04,内核为 3.19.0-20-generic

    ntp 包版本为 1:4.2.6.p5+dfsg-3ubuntu6

    注意闰秒开始时的故障:

    nuc@nuc1:~$ ./print_leap 
    1435703986.534020 1305198.740735208 1  1
    [... skipping to the second before the leap second]
    1435708798.991478 1310011.198081950 1  1
    1435708799.000905 1310011.207509156 1  1
    [... skipping to the leap second]
    1435708799.989309 1310012.195913702 1  1
    1435708800.000738 1310012.207343097 1  1
    1435708799.016079 1310012.222683886 3  1
    [... skipping to the second after the leap second]
    1435708799.999616 1310013.206220581 3  1
    1435708800.012446 1310013.219050861 4  1
    [... skipping to the last programmatic reading]
    1435708921.047985 1310134.254602197 4  1
    nuc@nuc1:~$ ./print_leap  # manual reading several hours later
    1435725807.234545 1327020.441295352 0  0
    

    系统 5:英特尔 NUC DN2820 运行 Ubuntu 14.04,内核为 3.13.0-53-generic

    ntp 包版本为 1:4.2.6.p5+dfsg-3ubuntu2.14.04.3

    nuc@nuc2:~$ ./print_leap 
    1435704031.137881 323125.995674014 1  1
    [... skipping to the second before the leap second]
    1435708798.995646 327893.853350053 1  1
    1435708799.007936 327893.865640804 1  1
    [... skipping to the leap second]
    1435708799.995589 327894.853293338 1  1
    1435708799.007527 327894.865231774 3  1
    [... skipping to the second after the leap second]
    1435708799.998111 327895.855815619 3  1
    1435708800.013461 327895.871165134 4  1
    [... skipping to the last programmatic reading]
    1435708921.282149 328017.139858311 4  1
    nuc@nuc2:~$ ./print_leap  # manual reading several hours later 
    1435725557.303859 344653.161689304 0  0
    

    系统 6:Lenovo E330 笔记本电脑运行 Ubuntu 14.04,内核 3.13.0-55-generic

    ntp 包版本为 1:4.2.6.p5+dfsg-3ubuntu2.14.04.3

    注意闰秒结束后的不一致。

    $ ./print_leap 
    1435706912.426035 82874.419021628 1  1
    [... skipping to the second before the leap second]
    1435708798.929115 84760.922063075 1  1
    1435708799.013439 84761.006388727 1  1
    [... skipping to the leap second]
    1435708799.981243 84761.974190967 1  1
    1435708799.049374 84762.042323977 3  1
    [... skipping to the second after the leap second]
    1435708799.913464 84762.906413341 3  1
    1435708800.000183 84762.993132942 3  1
    1435708800.477323 84763.470272391 4  1
    [... skipping to a change in the ntpq reading]
    1435708840.094271 84803.087225581 4  1
    1435708840.618538 84803.611493081 4  0
    [... skipping to the last programmatic reading]
    1435708921.042020 84884.034975833 4  0
    $ ./print_leap  # manual reading several hours later 
    1435724912.944500 100875.937487693 0  0
    

    系统 7:运行 Ubuntu 14.04 和内核 3.13.0-53-generic 的 AMD Phenom CPU 的戴尔服务器

    ntp 包版本为 1:4.2.6.p5+dfsg-3ubuntu2.14.04.3

    注意闰秒开始和结束时的故障

    $ ./print_leap
    1435704125.933343 2210517.393979537 1  1
    [... skipping to the second before the leap second]
    1435708798.998012 2215190.458598770 1  1
    1435708799.002893 2215190.463480413 1  1
    [... skipping to the leap second]
    1435708799.994690 2215191.455273816 1  1
    1435708800.001917 2215191.462501431 1  1
    1435708799.013505 2215191.474092236 3  1
    [... skipping to the second after the leap second]
    1435708799.992212 2215192.452796670 3  1
    1435708800.000210 2215192.460794341 3  1
    1435708800.006819 2215192.467403224 4  1
    [... skipping to the last programmatic reading]
    1435708921.323808 2215313.784400730 4  1
    $ ./print_leap  # manual reading several hours later
    1435726838.319240 2233230.779876616 0  0
    

    系统 8:运行 Debian 6 的虚拟服务器,使用主机的内核 2.6.32-028stab094.3

    来宾系统中没有安装 ntp 包。 NTP 可能会或可能不会在主机上运行。主机向访客系统提供时间。

    $ ./print_leap 
    sh: /usr/bin/ntpq: No such file or directory
    1435704206.353115 114299811.979591258 1  0
    [... skipping to the second before the leap second]
    sh: /usr/bin/ntpq: No such file or directory
    1435708798.994303 114304404.620727643 1  0
    sh: /usr/bin/ntpq: No such file or directory
    1435708799.000291 114304404.626715249 1  0
    [... skipping to the leap second]
    sh: /usr/bin/ntpq: No such file or directory
    1435708799.999445 114304405.625868168 1  0
    sh: /usr/bin/ntpq: No such file or directory
    1435708799.004273 114304405.630696209 3  0
    [... skipping to the second after the leap second]
    sh: /usr/bin/ntpq: No such file or directory
    1435708799.998234 114304406.624656721 3  0
    sh: /usr/bin/ntpq: No such file or directory
    1435708800.003723 114304406.630146580 4  0
    [... skipping to the last programmatic reading]
    sh: /usr/bin/ntpq: No such file or directory
    1435708921.185876 114304527.812316105 4  0
    $ ./print_leap  # manual reading several hours later
    sh: /usr/bin/ntpq: No such file or directory
    1435727243.250130 114322849.876607481 0  0
    

    系统 9:运行 Debian 8 的虚拟服务器,使用主机的内核 2.6.32-37-pve

    来宾系统中没有安装 ntp 包。 NTP 可能会或可能不会在主机上运行。主机向访客系统提供时间。

    $ ./print_leap 
    sh: 1: /usr/bin/ntpq: not found
    1435704263.521402 627240.363389922 1  0
    [... skipping to the second before the leap second]
    sh: 1: /usr/bin/ntpq: not found
    1435708798.991150 631775.833122648 1  0
    sh: 1: /usr/bin/ntpq: not found
    1435708799.062267 631775.904240598 1  0
    [... skipping to the leap second]
    sh: 1: /usr/bin/ntpq: not found
    1435708799.986215 631776.828187732 1  0
    sh: 1: /usr/bin/ntpq: not found
    1435708799.061678 631776.903650529 3  0
    [... skipping to the second after the leap second]
    sh: 1: /usr/bin/ntpq: not found
    1435708799.987007 631777.828979335 3  0
    sh: 1: /usr/bin/ntpq: not found
    1435708800.061094 631777.903067236 4  0
    [... skipping to the last programmatic reading]
    sh: 1: /usr/bin/ntpq: not found
    1435708921.380148 631899.222124765 4  0
    $ ./print_leap  # manual reading several hours later
    sh: 1: /usr/bin/ntpq: not found
    1435727152.545742 650130.387735253 0  0
    

    NTP 公共池问题

    NTP pool 中的一些服务器做了not announce the leap second。运气不好的情况下,当池中的所有上游服务器都未能宣布闰秒时,您将永远不会知道它。因此,如果本地应用程序需要闰秒警告,则必须使用配置文件通知本地 NTP 守护进程有关闰秒。

    故障/不一致

    当顺序查询当前时间和当前闰秒状态时,很明显,由于两个函数调用之间经过的时间,值可能不一致。

    但是,已经观察到无法通过调用之间经过的时间来解释的其他不一致。两台计算机(系统 4 和 7)已开始闰秒,gettimeofday 返回的时间值为 1435708800,然后在下一次读数中切换回 1435708799。 两台计算机,系统 6 和 7,在闰秒后启动第二个,adjtimex 值仍指示我们处于闰秒内。

    结论

    两种建议的方法都适用于预期目的:在正确配置的 linux 系统上提前知道是否会发生闰秒以及何时发生。

    这种高级知识使应用程序能够确定 CLOCK_REALTIME 和 CLOCK_MONOTONIC 之间的增量,然后在闰秒附近使用 CLOCK_MONOTONIC 来确定时间。

    如果没有做好准备,任何方法都不能帮助确定闰秒附近的时间。但这不是我所要求的。

    这两种方法都需要额外的知识,即闰秒只会在 UTC 月末出现,以便进行准确的规划。上个月的 NTP 闰秒警告似乎会延续到下一个 UTC 月份的前几分钟,然后才会消失。因此,仅当 UTC 月末在不久的将来时才应解释它们。

    【讨论】:

      猜你喜欢
      • 2015-03-05
      • 2013-07-20
      • 1970-01-01
      • 1970-01-01
      • 2013-04-07
      • 2015-09-18
      • 2015-03-22
      • 1970-01-01
      • 2023-02-02
      相关资源
      最近更新 更多