感谢 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 月末在不久的将来时才应解释它们。