导读 | 第27届国内计量大会发表最迟不晚于2035年勾销引入闰秒,这一音讯引起轰动。上一次闰秒产生,对Reddit、Mozilla、FourSquare等都产生了肯定的问题,其中Reddit宕机工夫超过1个半小时!本栏目特邀腾讯后盾开发工程师陶松桥,带你是深刻理解闰秒的起源及其影响,并介绍各类零碎常见的闰秒解决办法,其中会分享TencentOS Server 操作系统的解决方案。

闰秒从何而来

世界上有几种计量工夫的形式:

世界时(UT1):是一种地理计量的形式,天文学家通过观测地球的自转,并将自转一周的工夫(一天)等分为86400份,每份为一秒,受潮汐等因素的影响,地球自转一周的工夫并不是恒定的,这也是造成闰秒景象的间接起因。

原子时(TAI):因为下面形容的世界时并不稳固,物理学家用更为稳固的量子计量的形式来统计工夫,1967年,国内计量大会用铯133(Cs133)原子基态的两个超精密能级之间的跃迁所对应辐射的9192631770个周期所继续的工夫定义为1秒,这个是目前最准确的工夫计量形式,其误差为1400000年一秒,根本可忽略不计。

协调世界时(UTC):又称世界规范工夫或世界协调工夫,UTC以TAI为根底,又要兼顾UT1,当UTC,和UT1之间的偏差靠近1秒时,国内地球自转和参考系服务(IERS)会提前6个月颁布下一次闰秒的工夫。

咱们将世界绝大多数中央时区的根本工夫称为协调世界时,即 UTC。它源自散布在世界一些国家的大量原子时钟。地球的自转并不是十分恒定的,有时会有一些变动,均匀自转速度会迟缓降落。这就是为什么会在 UTC 时标中插入所谓闰秒,它们可将 UTC 工夫过程调整到实在地球自转工夫。

为什么会多出这一秒呢?它的存在是因为决定昼夜更替的地球绕轴自转会在很长的一段时间内慢下来,次要是由月亮-太阳引力造成。另外,地球也受其外部(地核、地幔)和内部(气象、陆地)形成影响。目前,工夫次要由分属几个国家的 250 台原子时钟测量,这些原子钟是通过测量原子的能量转换程度工作。应用这些时钟计算 UTC,同时因为这个工夫测量原理周期性地与地球不同步,因而必须应用闰秒进行校对。另外,咱们必须思考到当初的一天比 1820 年的一天要长 2 毫秒。不出所料,地球自转缓缓就与 UTC 不同步了。

国内地球自转服务局IERS 测量的是实在地球自转,并决定何时插入闰秒。插入闰秒个别总是在某个月的最初一天进行,首选六月或十二月的 UTC 午夜工夫。过来每次增加闰秒都是在六月或十二月进行。是否增加闰秒的申明,会由 IERS 在其Bulletin C 中公布。目前,在可能增加闰秒日期前半年会颁布 Bulletin C 。

因为闰秒是在全世界同时插入,插入闰秒的本地(民用)工夫取决于本地工夫与 UTC 之间的偏差,例如:2015年7月1日产生闰秒时,在时区 UTC+8h(北京工夫) 中,闰秒会在时钟显示午夜后 8 小时的时候插入。

闰秒时计算北京工夫的规范办法为:

2015-07-01 07.59.572015-07-01 07.59.582015-07-01 07.59.592015-07-01 07.59.60 <-- 闰秒2015-07-01 08.00.002015-07-01 08.00.012015-07-01 08.00.02

如果零碎时钟采纳国内原子时(TAI),并应用正确的时区,那么就会列出 23:59:60。但因为在 Unix 的 UTC 应用中不存在 23:59:60,Linux内核会采纳倒回一秒的办法在 0:00 UTC 后第一次时钟更新时插入闰秒。在本地工夫计时中,依据不同的时区偏差,比方 UTC+8h,在TencentOS Server零碎中,您会察看到以下景象:

2015-07-01  07:59:58.0002015-07-01  07:59:58.5002015-07-01  07:59:59.0002015-07-01  07:59:59.5002015-07-01  08:00:00.000 <-- 插入闰秒2015-07-01  07:59:59.0002015-07-01  07:59:59.5002015-07-01  08:00:00.0002015-07-01  08:00:00.500

IERS 确定闰秒后,一些工夫流传服务还会公布闰秒告诉。这包含德国长波发射机 DCF77 和卫星巡航零碎 GPS 示例。因而,可解码从那些零碎获取信号的接收器也能够解码闰秒告诉。如果在所利用协定中蕴含闰秒信息(例如接收器传送的工夫字符串),则从那些接收器读取工夫的应用程序也能够确定闰秒告诉。请留神,工夫代码接收器只能将闰秒告诉转发到应用程序,同时正确计时。正确处理闰秒是应用程序和(/或)操作系统的工作。

从1972年到2020年,均匀每21个月就插入一次闰秒。然而,距离是十分不规则的,而且显著在减少。在1999年1月1日至2004年12月31日的六年中没有闰秒,但在1972-1979年的八年中有九个闰秒。自1972年协调世界时正式应用至今,寰球曾经施行了27次正闰秒调整,最近一次的闰秒调整是格林尼治工夫2016年12月31日。从协调世界时正式应用以来,地球自转始终处于一直减慢的趋势,因而迄今为止的闰秒都是正闰秒。但相干科研发现,自2020年年中以来,地球自转速率出现放慢趋势,这意味着将来也可能会呈现负闰秒目前TAI与UTC的秒差为37:

#  Value of TAI-UTC in second valid beetween the initial value until#  the epoch given on the next line. The last line reads that NO#  leap second was introduced since the corresponding date #  Updated through IERS Bulletin 64 issued in July 2022#  ##  File expires on 28 June 2023###    MJD        Date        TAI-UTC (s)#           day month year#    ---    --------------   ------   #    41317.0    1  1 1972       10    41499.0    1  7 1972       11    41683.0    1  1 1973       12    42048.0    1  1 1974       13    42413.0    1  1 1975       14    42778.0    1  1 1976       15    43144.0    1  1 1977       16    43509.0    1  1 1978       17    43874.0    1  1 1979       18    44239.0    1  1 1980       19    44786.0    1  7 1981       20    45151.0    1  7 1982       21    45516.0    1  7 1983       22    46247.0    1  7 1985       23    47161.0    1  1 1988       24    47892.0    1  1 1990       25    48257.0    1  1 1991       26    48804.0    1  7 1992       27    49169.0    1  7 1993       28    49534.0    1  7 1994       29    50083.0    1  1 1996       30    50630.0    1  7 1997       31    51179.0    1  1 1999       32    53736.0    1  1 2006       33    54832.0    1  1 2009       34    56109.0    1  7 2012       35    57204.0    1  7 2015       36    57754.0    1  1 2017       37

闰秒解决计划

1)运行NTP的零碎

零碎如果应用 NTP(网络工夫协定)守护过程(ntpd)将其本地计时与 NTP 服务器同步,则都应主动进行闰秒调整。进行闰秒调整的前一天,NTP 服务器应告诉其客户端第二天的 23:59:59 UTC 会产生产生闰秒,Linux 内核应通过两次显示第 60 秒或彻底删除它,以便增加或者删除额定一秒。因而,在闰秒调整期间,运行 NTP 的零碎应有如下计时显示:

2015-06-30 23:59:59 UTC2015-06-30 23:59:59 UTC2015-06-30 00:00:00 UTC

产生闰秒时,内核会在零碎 log 中写入信息:

Jul  1 07:59:59 TENCENT64 kernel: [579201.951291] Clock: inserting leap second 23:59:60 UTC

应用ntpdate命令形式,与ntp服务器进行工夫同步的零碎,将不会通过ntp服务器接管到闰秒告诉,而是在系统管理员指定的时刻与ntp服务器进行工夫同步。例如,系统管理员设定每小时的第52分与ntp服务器进行工夫同步,那么在7月1日08:00 CST到09:52之间,零碎工夫与ntp服务器工夫会相差1秒(快1秒)。

2)运行PTP的零碎

PTP(准确工夫协定)中替换的工夫戳通常采纳不蕴含闰秒的TAI(国内原子时);但 ptp4l 和 phc2sys 将设置内核标签,插入闰秒以便零碎时钟持续以 UTC 运行。而后该内核就能够失常插入闰秒。

3)未运行NTP或者PTP的零碎

默认状况下,不应用 NTP 或者 PTP 同步其计时的 Linux 零碎不会修改闰秒,且这些零碎报告的工夫与修改闰秒后的 UTC 工夫有一秒钟的差异。闰秒产生后应手动重置时钟。

您还能够将 tzdata 更新至最新版本,将 /usr/share/zoneinfo/right 目录层级中的正确文件复制到 /etc/localtime,并将时钟重置到正确的本地工夫,以便将这些系统配置可正确报告工夫。  /usr/share/zoneinfo/right 中的文件蕴含自该世纪开始,从 1970 年 1 月 1 日 00:00:00 UTC 产生的所有闰秒修改的本地工夫信息。  /usr/share/zoneinfo 中的其余时区文件未增加闰秒修改。 从1972年至今,共增加了 27次闰秒。

例如:如果某个零碎位于中国时区,您能够将其重新配置为通过运行以下命令报告闰秒修改工夫,

cp /usr/share/zoneinfo/right/Asia/Shanghai /etc/localtime

例如在TS2零碎中,tzdata包的版本为tzdata-2015a-1.tl2.noarch,执行完上述拷贝后,则会在闰秒产生工夫2015年7月1日8点主动插入闰秒。

4)windows零碎

晚期的Windows版本(Win10版本以前) 工夫服务并不示意 Leap 指标的值,当 Windows 工夫服务接管到的数据包,包含闰秒。因而,闰秒产生后,正在运行 Windows 工夫服务的 NTP 客户端会比理论工夫快一秒。这种工夫差别在下次同步时解决。

从 Windows 10 Redstone 5 和 Windows Server 2019 起,微软的操作系统能以更准确、UTC 兼容和可追踪的形式解决闰秒。不过从2017年至今,没有产生过闰秒了。

历史影响

对于日常生活而言,失常的下班、上班、工作、学习,生命中偏差的这一秒无关痛痒。然而闰秒对于准确要求工夫的行业如航空、航天、军工等,会产生较大影响。对于服务器清一色linux零碎的互联网行业而言,闰秒可能会造成机器cpu忽然增高,机器宕机、对应的服务挂掉。随着linux的广泛应用,闰秒的影响也被越来越多的被关注。

历史上,因为linux内核的一些问题,闰秒对系统造成屡次影响。比方CPU利用率高会给生产环境带了不少挑战。2012年施行闰秒时,国外不少出名网站呈现了长期服务中断。当2015年闰秒再度来长期,工程师们修复了局部2012年呈现的问题,但却东窗事发——发现了新的问题。后续亦是如此。闰秒让互联网企业如鲠在喉。

1) linux-2.6.22以前内核版本的闰秒死锁

07年的commit:

http://git.kernel.org/?p=linu...;a=commitdiff;h=746976a301ac9c9aa10d7d42454f8d6cdad8ff2b;hp=872aad45d6174570dd2e1defc3efee50f2cfcc72

每次时钟中断触发时会调用 tick\_do\_update\_jiffies64 更新 jiffies 的 值。因而在更新前对 xtime\_lock 加了写锁。闰秒产生时,开发者须要修改 jiffies 的值。在 tick\_do\_update\_jiffies64 外面最终会调用到 second\_overflow 这个函数,以解决润秒。在函数 second\_overflow 外面,解决润秒的减少和缩小前都调用了一个 clock\_was\_set 函数。该函数外部,申请了 xtime\_lock 的读锁。此时,与先前的写锁产生死锁。

该patch在linux内核版本2.6.22中引入,所以只有2.6.22内核之前的零碎可会呈现该问题,也就是影响sles10和centos5.5零碎。在sles10和centos5.5中,clock\_was\_set()因不反对高精度时钟而被定义为空,所以不造成影响。

2)linux-2.6.25到2.6.27内核版本的零碎死锁

Bug 479765 - Leap second message can hang the kernel 形容了leap second会对系统产生影响的起因:

当一个leap second被插入或删除时,内核会打印一条相干信息:

[69596.647516] Clock: inserting leap second 23:59:60 UTC

而该信息的打印会因xtime\_lock而造成零碎死锁。

上面是2.6.26内核下该问题呈现时的栈信息(this is with Fedora 8 and

kernel kernel-2.6.26.6-49.fc8.x86\_64):

#0  ktime_get_ts (ts=0xffffffff8158bb30) at include/asm/processor.h:691#1  0xffffffff8104c09a in ktime_get () at kernel/hrtimer.c:59#2  0xffffffff8102a39a in hrtick_start_fair (rq=0xffff810009013880,     p=<value optimized out>) at kernel/sched.c:1064#3  0xffffffff8102decc in enqueue_task_fair (rq=0xffff810009013880,     p=0xffff81003fb02d40, wakeup=1) at kernel/sched_fair.c:863#4  0xffffffff81029a08 in enqueue_task (rq=0xffffffff8158bb30,     p=0xffff81003b8ac418, wakeup=-994836480) at kernel/sched.c:1550#5  0xffffffff81029a39 in activate_task (rq=0xffff810009013880,     p=0xffff81003b8ac418, wakeup=20045) at kernel/sched.c:1614#6  0xffffffff8102be38 in try_to_wake_up (p=0xffff81003fb02d40,     state=<value optimized out>, sync=0) at kernel/sched.c:2173#7  0xffffffff8102be9c in default_wake_function (curr=<value optimized out>,     mode=998949912, sync=20045, key=0x4c4b40000) at kernel/sched.c:4366#8  0xffffffff810492ed in autoremove_wake_function (wait=0xffffffff8158bb30,     mode=998949912, sync=20045, key=0x4c4b40000) at kernel/wait.c:132#9  0xffffffff810296a2 in __wake_up_common (q=0xffffffff813d3180, mode=1,     nr_exclusive=1, sync=0, key=0x0) at kernel/sched.c:4387#10 0xffffffff8102b97b in __wake_up (q=0xffffffff813d3180, mode=1,     nr_exclusive=1, key=0x0) at kernel/sched.c:4406#11 0xffffffff8103692f in wake_up_klogd () at kernel/printk.c:1005#12 0xffffffff81036abb in release_console_sem () at kernel/printk.c:1051#13 0xffffffff81036fd1 in vprintk (fmt=<value optimized out>,     args=<value optimized out>) at kernel/printk.c:789#14 0xffffffff81037081 in printk (    fmt=0xffffffff8158bb30 "yj$\201????\2008\001\t") at kernel/printk.c:613#15 0xffffffff8104ec16 in ntp_leap_second (timer=<value optimized out>)    at kernel/time/ntp.c:143#16 0xffffffff8104b7a6 in run_hrtimer_pending (cpu_base=0xffff81000900f740)    at kernel/hrtimer.c:1204#17 0xffffffff8104b86a in run_hrtimer_softirq (h=<value optimized out>)    at kernel/hrtimer.c:1355#18 0xffffffff8103b31f in __do_softirq () at kernel/softirq.c:234#19 0xffffffff8100d52c in call_softirq () at include/asm/current_64.h:10#20 0xffffffff8100ed5e in do_softirq () at arch/x86/kernel/irq_64.c:262#21 0xffffffff8103b280 in irq_exit () at kernel/softirq.c:310#22 0xffffffff8101b0fe in smp_apic_timer_interrupt (regs=<value optimized out>)    at arch/x86/kernel/apic_64.c:514#23 0xffffffff8100cf52 in apic_timer_interrupt ()    at include/asm/current_64.h:10#24 0xffff81003b9d5a90 in ?? ()#25 0x0000000000000000 in ?? ()

从下面的栈信息咱们能够发现:该问题的呈现起因是当对leap second进行操作(插入或删除)之前,曾经获取了xtime\_lock锁;而之后在调用printk()打印日志信息时,printk()中会尝试唤醒klogd内核线程,在唤醒过程中会调用到偏心调度类的相干函数,其中会调用ktime\_get()获取工夫信息,其中会再次尝试获取xtime\_lock锁,从而造成死锁

该景象局部因为hrtick\_start\_fair()函数的引入。是由commit 8f4d37ec (high-res preemption tick)引发,这大略在2.6.25版本引入。然而在2.6.25之前的内核,不会产生这个死锁。

2.6.28版本引入了commit b845b517。printk()中的wake\_up\_klogd()不会间接wake\_up klogd(),也就不会触发后续的xtime\_lock,最终防止了死锁的产生。所以,该起因引起的零碎死锁只可能产生在linux内核2.6.25到2.6.27版本下

Sles11应用2.6.27内核,属于比拟危险的局部内核。然而Novell宣称曾经引入了commit b845b517b5e3706a3729f6ea83b88ab85f0725b0,因此不存在该问题,而且几个小时的试验后零碎依然失常。

此问题影响的版本还有 RHEL4:kernel-2.6.9.89.EL之前的版本,RHEL5.3:kernel-2.6.18-128.37.1.el5之前的版本。现网centos5.5应用的内核版本是2.6.18-194.el5,其不受影响。

3)linux-3.4内核版本的零碎活锁

08年的commit中为了解决之前遇到的leap second问题而将对leap second的解决从second\_overflow()中独立进去,应用定时器来实现此工作。

然而12年的commit认为该patch存在如下可能的livelock场景:

CPU 0                                                                         CPU 1do_adjtimex()spin_lock_irq(&ntp_lock);process_adjtimex_modes();                                   timer_interrupt()process_adj_status();                                        do_timer()ntp_start_leap_timer();                                     write_lock(&xtime_lock);hrtimer_start();                                             update_wall_time();hrtimer_reprogram();                                     ntp_tick_length()tick_program_event()                                     spin_lock(&ntp_lock);clockevents_program_event()ktime_get()seq = req_seqbegin(xtime_lock);

问题在于,引入ntp\_lock的commit(http://patches.linaro.org/5122/)

是在3.4内核版本,且在3.4内核失去了修复。所以此问题对3.4以前和当前的内核无影响。

08年的commit:https://git.kernel.org/cgit/l...

12年的commit:

https://lkml.org/lkml/2012/3/...

4)linux-2.6.32内核插入闰秒可能呈现高CPU耗费

2012年的闰秒插入过后导致了一些互联网公司的服务器高cpu耗费,其问题本源在以下网址失去了论述:https://lkml.org/lkml/2012/7/...

leap-a-day.c为一个小测试程序,编译后加-s参数运行,可每10秒插入或者删除一个闰秒,用户可自行下载编译测试。2015年7月1日的闰秒将会呈现以下景象:

Setting time to Wed Jul  1 07:59:50 2015Scheduling leap second for Wed Jul  1 08:00:00 2015Wed Jul  1 07:59:57 2015 +     98 us (3883)     TIME_INSWed Jul  1 07:59:57 2015 + 500248 us (3883)     TIME_INSWed Jul  1 07:59:58 2015 +    366 us (3883)     TIME_INSWed Jul  1 07:59:58 2015 + 500483 us (3883)     TIME_INSWed Jul  1 07:59:59 2015 +    598 us (3883)     TIME_INSWed Jul  1 07:59:59 2015 + 500740 us (3883)     TIME_INSWed Jul  1 07:59:59 2015 +    910 us (3883)     TIME_OOPWed Jul  1 07:59:59 2015 + 501046 us (3883)     TIME_OOPWed Jul  1 08:00:00 2015 +   1214 us (3884)     TIME_WAITWed Jul  1 08:00:00 2015 + 501359 us (3884)     TIME_WAITWed Jul  1 08:00:01 2015 +   1481 us (3884)     TIME_WAITWed Jul  1 08:00:01 2015 + 501599 us (3884)     TIME_WAITWed Jul  1 08:00:02 2015 +   1650 us (3884)     TIME_WAIT

咱们测试后发现,在TS1.2发行版下,可呈现“ERROR: hrtimer early expiration failure observed”提醒。

/* Test for known hrtimer failure */void test_hrtimer_failure(void){         struct timespec now, target;         clock_gettime(CLOCK_REALTIME, &now);         target = timespec_add(now, NSEC_PER_SEC/2);         clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &target, NULL);         clock_gettime(CLOCK_REALTIME, &now);          if (!in_order(target, now)){                   printf("ERROR: hrtimer early expiration failure observed.\n");         }

剖析代码能够发现:应用clock\_nanosleep(CLOCK\_REALTIME, TIMER\_ABSTIME, &target, NULL);这种定时器形式,在插入闰秒后,该定时器本应该0.5秒到期,却立即到期。实质起因是内核中记录时间的数据结构中并没有表白闰秒的中央,因而在减少闰秒时须要特地调整这些数据结构。而很多定时器并不间接应用“相对”时钟而应用绝对的工夫距离,这样,在定时器代码中就应该对闰秒做额定的查看。

但问题是这样的查看之前被删掉。对于许多利用来说,定时器的一次提前触发并不是什么问题。但有些定时器则不然,他们会重复启动本人,这样的结果就是它们重复地被疾速唤醒,于是零碎负载就呈现了察看到的尖峰景象。闰秒的插入没有调用clock\_was\_set(),来揭示hrtimer子系统扭转。定时器在插入闰秒后,其基准比零碎工夫快一秒,因而会提前一秒到期。

在察看到cpu高耗费后,解决办法很简略,执行下述命令即可:

date -s "`date`"  

其原理就是date再设置一下以后零碎工夫,clock\_settime(CLOCK\_REALTIME,&ts)会调用clock\_was\_set()。为了应答ntpd同步可能呈现的该问题,咱们在2015年特意编写了一个解决程序,该程序通过编译后能够增加到crontab工作:

58 7 1 7 * /data/solve_hrtimer_failure.o > /data/solve_hrtimer_failure.log 2>&1

在7月1日7点58分开始,每隔100ms检测闰秒是否插入了,当插入闰秒后,该程序调用clock\_settime函数,进而修复了该问题。

勾销闰秒

1)为何勾销闰秒

对闰秒最为敏感的莫过于计算机相关畛域。因为闰秒的呈现没有固定法则,对应的工夫调整无奈从一开始就写在计算机程序里。在万物互联时代,很多畛域都依靠计算机网络传输信息,施行闰秒也会影响航空、通信、金融及其他须要精准对时的畛域。

往年7月Meta公司两名工程师发文称:“闰秒是一种弊大于利的冒险做法,咱们认为当初是时候引入新技术来取代它了。”这一表态引来各大公司称道。

2)勾销闰秒的后续可能

负责协调世界时的国内计量局(BIPM)示意,科学家和政府代表18日在法国举办的一次会议上投票决定到2035年勾销闰秒。BIPM工夫部门负责人帕特里齐亚·塔维拉示意,这项“历史性决定”将容许“秒数间断流动,而不会呈现目前由不规则闰秒造成的不连续性。

闰秒是目前把世界时和国内原子时分割起来的伎俩。因为世界时是基于地球自转确定的,又称地理时或太阳时。没有闰秒意味着人们应用的工夫与地球自转、太阳地位不关联,工夫和天文学出现割裂状态。

第27届国内计量大会决议要求多机构协商,提出一个能够将协调世界时继续至多百年的新计划并制订施行打算,纳入下一届大会的决定草案中。依据决定,闰秒将临时持续失常增加。但到2035年,世界时和国内原子时之间的差别将被容许增长到大于一秒的值。

兴许解决这个问题的可能办法是让世界时和国内原子时之间的差别减少到一分钟,但专家预计调整时长在50到100年之间。而有提议指出,无需在时钟上减少闰分钟,而是将某一天的最初一分钟变为须要两分钟;也有人倡议进行校对,同时颁布世界时和国内原子时之间一直增长的时刻差。

腾讯工程师技术干货中转:

1、算法工程师深度解构ChatGPT技术

2、10分钟!从架构视角读懂K8s

3、探秘微信业务优化:DDD从入门到实际

4、祖传代码重构:从25万行到5万行的血泪史