1. 时间准确重要么?

很多人都认为系统时间是个无关紧要的东西,大概准确就行了,就算差个半小时只要自己不误会也没问题。但是,这在当今与网络联系密切的现代操作系统上也许并不是那么一回事——尤其是Linux。

Linux最大的用途就是服务器了。作为一台服务器,时间准确是十分重要的,以防出现一些奇怪的情况——例如一台邮件服务器时间快了,别人收到的邮件也许就会是来自“未来”的了。相信运维们都很了解时间的重要性,我也不再赘述了。

不过也许很多人不知道的是,桌面系统时间不准确也会引发问题。我这几天在GNOME 3中就遇到了一个由时间引起的诡异问题:GNOME 3.2中引入了“在线账户”(GNOME Online Account),可以与在线账户同步文档、聊天、联系人等等,目前暂时仅支持Google。但几天前,我的在线账户突然间授权失效了,重新登陆也不行,总是获取token失败。查看Debug输出、日志等等,都没有发现什么有价值的线索。后来无意间我发现系统时间慢了半小时,调整之后再尝试登陆在线账户,竟然成功了。原来是由于系统时间偏差过大引起的。

其实类似的问题在很多“time-based”的token验证系统(简单说来就是根据当前时间产生token的系统)上都会发生,如果系统时间偏差过大将会导致验证失败。

2. 硬件时间 & 系统时间

我们的电脑上其实有着两套时间——一个是由主板上的时间芯片产生的硬件时间,也可以叫做BIOS时间,只要主板电池有电就会走;另一个则是由操作系统维护的系统时间,只在运行时有意义。不要以为系统时间不过是简单的读取硬件时间(否则系统时间也没有存在的意义了)。Linux会在开机时读取硬件时间初始化系统时间,而后通过计时器中断来维护它。Linux上的系统时间其实是从1970年开始至今的秒数,而且,它还是一个实数(虚拟精度无穷大)。在关机时,Linux会将系统时间写回硬件时间。(注意,这仅是默认的传统方式,由hwclock服务维护。)

现代操作系统使用系统时间的原因是硬件时间毕竟要通过BIOS读取,无法提供足够的精度和速度。而且全球化背景下时区、夏令时、冬令时等各种时间变化较为复杂,使用系统时间能够更为灵活——因而,大部分现代操作系统(除了Windows)都建议将BIOS硬件时间设置为UTC时间(即伦敦格林威治时间),而将复杂的时间管理交由系统执行。Windows以“为了让客户在BIOS设置里看到‘熟悉’的当地时间”以及向后兼容原因,默认将硬件时间当成localtime(当地时间)。如果你使用多系统,且不希望在Linux或Mac下使用落后的localtime,想要让Windows能够支持UTC硬件时间,可以参考这篇博客,修改注册表。

(有关此内容的更多详情,可参见hwclock manpage中的NOTES。)

3. 保持时间准确

其实,使用系统时间的另一个原因是——硬件时间并不准确。主板的时间芯片会产生较为明显的“漂移”(drift),例如每天慢或快几秒钟。相比之下,计时器中断要更加准确。所以Linux的hwclock在关机时将系统时间写回硬件的做法是为了纠正硬件时间的偏移,并且,如果配置正确,hwclock还会将偏移量记录下来,以计算出硬件时间的平均偏移速率,这样在下次开机时,hwclock会根据当前时间和记录下来的上一次调整时间的差,按照平均偏移速率来做一次矫正,这样可以极大提高硬件时间的准确性。

然而,这样的方法比较适用于长时间开机的服务器,才能够得出比较准确的偏移速率。对于频繁开关机的桌面系统,效果并不好。这时候只能求助于网络时间同步ntp了。

网络上不乏ntp的教程,但是大多使用的是即将被淘汰的ntpdate。现在推荐的ntp使用方式是配置好/etc/ntp.conf,再使用ntpd -q

具体如下。编辑/etc/ntp.conf,修改server相关配置(可前往http://www.pool.ntp.org寻找合适的服务器)。例如:

#适合中国用户,iburst选项在第一次链接失败时发送burst包重试,推荐使用
server 2.cn.pool.ntp.org iburst
server 1.asia.pool.ntp.org iburst
server 0.asia.pool.ntp.org iburst

然后运行ntpd -gq && hwclock -w即可(g选项允许调整15分钟以上,q选项是在同步成功后退出;之后写入硬件时间)。

然而,这种方式仅会同步一次时间。ntp推荐的使用方式是作为daemon运行,自动同步时间。并且在这种模式下,ntp会激活内核的“11分钟模式”,每11分钟将系统时间写入到硬件时间。因此与hwclock服务冲突,应将其禁用。

你可以直接将ntpd放入rc.d中(并将hwclock移除)。然而,ntpd仅在有网络时可用,对于笔记本,你也许希望在连接后启用ntpd,断线后结束。如果你用NetworkManager管理网络,那么你可以通过dispatcher脚本来实现这个功能。这里给个例子:

#!/bin/sh

INTERFACE=$1 # The interface which is brought up or down
STATUS=$2 # The new state of the interface

case "$STATUS" in
    'up') # $INTERFACE is up
	exec /etc/rc.d/ntpd start
	;;
    'down') # $INTERFACE is down
	# Check for active interface and down if no one active
	if [ ! `nm-tool|grep State|cut -f2 -d' '` = "connected" ]; then
		exec /etc/rc.d/ntpd stop
	fi
	;;
esac

将以上脚本放在/etc/NetworkManager/dispatcher.d/下,并将权限改为755即可。(再次强调,请不要同时使用hwclock服务!)