跳转到内容

系统时间

来自 Arch Linux 中文维基

这篇文章的某些内容需要扩充。

原因: 文章内容基本上与 systemd timedatectl 关联;需要先解释如 datehwclock 等基础命令的作用 (在 Talk:系统时间 中讨论)

在操作系统中,时间(时钟)取决于三个部分:时间值(本地时间或 UTC 等)、时区及夏令时(DST,需要时应用,在中国已废止)。本文将分别介绍各个部分的定义及如何设置和读取它们。系统中也存在两种时钟:硬件时钟和系统时钟,下文也将进行详细描述。

大部分操作系统的时间管理包括如下方面:

  • 启动时根据硬件时钟设置系统时间
  • 保持系统时钟准确,细节请参考#时钟同步
  • 关机时根据系统时钟设置硬件时钟

时间标准[编辑 | 编辑源代码]

时间表示有两个标准:localtimeUTCCoordinated Universal Time)。localtime 标准依赖于当前时区,而 UTC 是与时区无关的全球时间标准。尽管概念上有差别,UTC 和 GMT(格林威治时间)是一样的。

硬件(CMOS 时钟,即 BIOS 时钟)所使用的时间标准由操作系统设定,Windows 默认使用 localtime,macOS 默认使用 UTC ,而其他的 UNIX 和类 UNIX 操作系统各不相同。使用 UTC 标准的操作系统通常会认为硬件时钟也是 UTC,并在启动时根据时区调整到对应时间。

硬件时钟[编辑 | 编辑源代码]

硬件时钟(即实时时钟 RTC 或 CMOS 时钟)仅能保存:年、月、日、时、分、秒这些时间数值。UEFI 固件可以额外存储时区以及是否使用夏令时的信息。

读取硬件时钟[编辑 | 编辑源代码]

# hwclock --show

根据系统时钟设置硬件时钟[编辑 | 编辑源代码]

下面命令将系统时钟设置到硬件时钟,同时会创建或更新 /etc/adjtime。详情请参考 hwclock(8) § The Adjtime File 和后面的 #时钟偏移段落。

# hwclock --systohc

自动同步[编辑 | 编辑源代码]

默认配置下,Arch Linux 内核会每 11 分钟同步一次硬件时钟和系统时钟。你可以使用以下命令查看你的内核中是否已启用该功能:

$ zgrep CMOS /proc/config.gz
CONFIG_GENERIC_CMOS_UPDATE=y
CONFIG_RTC_DRV_CMOS=y

首次同步发生在启动阶段,也就是说如果硬件时钟误差较大(例如 CMOS 电池失效导致时钟被重置到 2000 年),那任何需要准确时间的操作都将报错(包括 SSL 和在线证书状态协议(OCSP))。浏览器通常会将硬件时钟附在发往网站的请求中,因此如果时钟误差过大,就会因 OCSP 错误导致浏览器拒绝进行连接。

系统时钟[编辑 | 编辑源代码]

系统时钟(即软件时间)保存了:时间、时区和夏令时设置。Linux 内核将其计算为自 UTC 时间 1970 年 1 月 1 日经过的秒数。初始系统时钟是从硬件时间计算得来,计算时会考虑 /etc/adjtime 的设置。系统启动之后,系统时钟与硬件时钟独立运行,Linux 内核通过时钟中断计数维护系统时钟。

读取系统时间[编辑 | 编辑源代码]

下面命令可以获得系统时间(同时以 localtime 和 UTC 显示)和 RTC 时间(硬件时钟):

$ timedatectl

设置系统时钟[编辑 | 编辑源代码]

设置系统时间的本地时间:

# timedatectl set-time "yyyy-MM-dd hh:mm:ss"

例如:

# timedatectl set-time "2014-05-26 11:13:54"

将时间设置为 2014 年 5 月 26 日 11 时 13 分 54 秒。

多系统[编辑 | 编辑源代码]

如果设备上安装了多个操作系统,它们会从同一个硬件时钟生成自己的当前时间;在这种情况下,建议将硬件时钟设为 UTC,以避免各操作系统之间的冲突。如果硬件时钟使用的是本地时间,那可能会有多个操作系统同时修改硬件时钟(如进行夏令时调整),导致修正过度;另外,在跨多个时区出行的场景下,使用其中一个操作系统重置系统/硬件时钟也可能会产生问题。

通过 timedatectl 命令可以查看并设置硬件时间。通过下面命令查看 Arch 系统当前硬件时钟的时间标准:

$ timedatectl | grep local
RTC in local TZ: no

将硬件时间设置为 localtime:

# timedatectl set-local-rtc 1

硬件时间设置成 UTC:

# timedatectl set-local-rtc 0

上述命令会自动生成 /etc/adjtime 并配置 RTC,无需单独进行设置。

系统启动载入 RTC 驱动时可能会根据硬件时钟设置系统时钟。是否设置取决于硬件平台、内核版本和内核编译选项。如果进行了该设置,此时会假定硬件时钟为 UTC 标准,/sys/class/rtc/rtcN/hctosys(N=0,1,2,..) 会设置成 1。

后面 systemd 会根据 /etc/adjtime 重新由硬件时钟生成系统时间。所以在硬件时钟使用当地时间时,可能导致启动阶段出现预期外的行为,如系统时间回倒(更多信息)。所以从 systemd 216 版本开始,如果 RTC 设置为本地时间而不是 UTC,systemd 不会将时间回写到硬件时钟,因为这样会引起 Windows 时间错乱。systemd 也不会将当前时区信息传递给内核,这意味着 FAT 时间戳将统一按 UTC 时间处理 [1]

注意:
  • 使用 timedatectl 需要一个启动的 D-Bus。因此,在 chroot(比如在安装系统时)下可能无法运行 timedatectl。在这些情况下, 你可以使用 hwclock 命令或使用 systemd-nspawn 代替 chroot。
  • 如果 /etc/adjtime 不存在,systemd 会假设硬件时钟设为 UTC。

Windows 系统使用 UTC[编辑 | 编辑源代码]

Arch + Windows 双系统时,建议配置 Windows 使用 UTC 时间,而不是让 Linux 使用 localtime。(Windows 默认使用 localtime [2]

这一步可以通过修改注册表值达成:打开 regedit,然后新建一个 DWORD 类型的值,位置为 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation\RealTimeIsUniversal,值为十六进制的 1

也可以用管理员权限启动命令行,然后执行以下命令来完成:

C:\>reg add "HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TimeZoneInformation" /v RealTimeIsUniversal /d 1 /t REG_DWORD /f

或者,在桌面上创建一个 *.reg 文件,写入以下内容,并双击运行它以将内容导入到注册表:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation]
"RealTimeIsUniversal"=dword:00000001

如果 Windows 要求根据夏令时更新时钟,可以允许。时钟仍然是 UTC,仅是显示时间会改变。

在设置该值后,可以会需要更新#硬件时钟#系统时钟值。

如果你有时间偏移问题,重新安装 tzdata 并再次设置你的时区:

# timedatectl set-timezone Asia/Shanghai

UTC 在 Ubuntu/Fedora 下的设置[编辑 | 编辑源代码]

很多 Linux 发行版会在安装时检测计算机上是否存在 Windows,若存在则会默认使用localtime。这一操作似乎是为了让新用户在不修改注册表的前提下,在 Windows 设备上试用 Linux。

要修改该行为,请参考上文。

多 NTP 交互[编辑 | 编辑源代码]

If you use an NTP client (see #时钟同步 below) that keeps track of RTC drift on any system, you should disable time synchronization on all but one system. Otherwise the NTP clients would be unaware of each other's adjustment and make grossly incorrect estimates of the RTC drift.

For Windows, go to the Date and time settings and uncheck the time sync option. You can also run w32tm /unregister as an administrator to unregister the time-sync service: Active Directory machines are known to ignore the synchronization settings and perform a synchronization anyways to prevent replay attacks. The Windows clock synchronization routine is quite inaccurate to start with, requiring even extra work to reach one-second accuracy, so disabling it should not be much of a loss.

时区[编辑 | 编辑源代码]

检查当前时区:

$ timedatectl status

显示可用时区:

$ timedatectl list-timezones

修改时区:

# timedatectl set-timezone <Zone>/<SubZone>

例如:

# timedatectl set-timezone Asia/Shanghai

此命令会创建一个 /etc/localtime 软链接,指向 /usr/share/zoneinfo/ 中的时区文件,如果你要手动创建链接(如在 chroot 中无法使用 timedatectl 的情况下),请确保创建的是软链接(参阅 localtime(5) § DESCRIPTION):

手动创建(例如 chroot 中 无法执行 timedatectl)此链接请确保是相对链接而不是绝对链接,参阅localtime(5) § DESCRIPTION

# ln -sf /usr/share/zoneinfo/Zone/SubZone /etc/localtime
提示:也可以使用 tzselect 交互式选择时区。

详情请参考 timedatectl(1)localtime(5)

基于地理位置设定[编辑 | 编辑源代码]

注意: 有些桌面环境支持自动选择时区(例如 GNOME#日期与时间)。

要根据 IP 位置自动设置时区,可以使用地理位置 API 来获取时区信息,例如 curl https://ipapi.co/timezone,然后将输出传递给 timedatectl set-timezone 进行自动设置。有些 IP 位置 API 提供免费服务或免费额度,可参考以下清单:

在每次 NetworkManager 连接到网络时更新时区[编辑 | 编辑源代码]

创建一个 NetworkManager 调度脚本

/etc/NetworkManager/dispatcher.d/09-timezone
#!/bin/sh
case "$2" in
    up)
        timedatectl set-timezone "$(curl --fail https://ipapi.co/timezone)"
    ;;
esac
提示:使用 connectivity-change 取代 up 可避免使用 OpenConnect 等 VPN 客户端时误修改时区。

另外,tzupdateAUR 可以根据 IP 地址的地理位置自动修改时区。该热门 IP 地理位置 API 对比可能有助于在生产环境中决定使用哪个 API。

时钟偏移[编辑 | 编辑源代码]

所有的时钟都距“真实时间”(代表为国际原子时钟)有一定偏差,世界上没有完美的时钟。石英电子钟的时间是不准确的,但会有固定的误差,这里的“误差”即“时钟偏移”。

hwclock 设置硬件时间时,会计算每天偏移的秒数。偏移值是原硬件时间与新设置硬件时间的差,并且考虑上次硬件时间设置时的偏移与上次设置值时的时间。新的偏移值及设置值的时间会写入到 /etc/adjtime,并覆写掉上次的值。因此,在运行 hwclock --adjust 命令时,可以根据偏移修正硬件时钟。如果启用了 hwclock 守护进程,那该步骤在关机时也会进行,但对于使用 systemd 的 Arch 类型系统而言不会出现该情况。

注意: 如果硬件时间值与原值的差小于 24 小时,偏移量不会重新计算,因为时间过短,无法精确设置偏移。
注意: 如果在 24 小时内再次设置了硬件时钟,由于 hwclock 认为时间间隔过短无法精确计算偏移,其不会重新计算偏移值。

如果硬件时钟总是大幅过快或过慢,可能是计算了错误的偏移值(仅适用于运行了 hwclock 守护进程的情况)。该问题会在硬件时钟设置错误,或时间标准与同设备下 Windows 或 macOS 系统不一致的情况下出现。删除文件 /etc/adjtime 可以删除偏移值,然后重新设置正确的硬件时钟和系统时钟,并检查时间标准是不是设置正确。

注意: 如果在使用 systemd 时要使用 /etc/adjtime 中的偏移值(例如无法或不想使用 NTP 时),需要检查调用 hwclock --adjust 命令,该操作可通过 cron 定时任务实现。

软件时钟非常精确,但与大多数时钟一样,它也会出现偏移。在极少数情况下,如果内核略过了系统中断,那系统时间就会出现偏差。有些工具可以提高软件时钟的精确度:

时钟同步[编辑 | 编辑源代码]

网络时间协议(NTP)是一个通过包交换和可变延迟网络来同步计算机系统时间的协议。

网络时间协议(NTP)[编辑 | 编辑源代码]

在 RFC 定义的完整 NTP 支持中,客户端必须能够从多个服务器合并时间,补偿延时并跟踪系统(软件)时钟偏移。以下为 Arch Linux 下可用的 NTP 实现:

  • Chrony — 是一个客户端和服务器,适合漫游场景,是为不能始终保持在线的系统而特别设计。在多数情况下比 ntpd 收敛更快,并更接近参考值。可以跟踪硬件时钟(RTC)偏移。
https://chrony-project.org/ || chrony
https://www.ntp.org/ || ntp
  • NTPsec — 专注于安全的 NTPd 分支。移除了大量旧代码,但在使用上区别不大。
https://ntpsec.org/ || ntpsecAUR

简单网络时间协议(SNTP)[编辑 | 编辑源代码]

功能上比标准 NTP 节点少的都被认为是简单网络时间协议(SNTP)。最简单的 SNTP 客户端会从单台服务器获取时间,不跟踪时钟偏移并直接将其设置到系统上。SNTP 的准确度相对较低,但消耗的系统资源更少。其提供的准确度适用于桌面和嵌入式环境,但不适合用于 NTP 服务器上。以下为 SNTP 的实现:

  • ConnMan — 带 SNTP 支持的轻量网络管理器。
https://01.org/connman (waybackmachine) || connman
  • ntpclient — 是简单的命令行 NTP 客户端。
http://doolittle.icarus.com/ntpclient/ || ntpclientAUR
  • OpenNTPD — 是 OpenBSD 项目的一部分,同时实现了 SNTP 客户端和服务器。不支持闰秒。
https://www.openntpd.org/ || openntpd
  • sntp — NTPd 附带的 SNTP 客户端。它取代了 ntpdate ,并被推荐用于非服务器环境。
https://www.ntp.org/ || ntp
  • systemd-timesyncd — 是一个简单的 SNTP 守护进程。它只实现了客户端,专用于从远程服务器查询时间,更适用于绝大部分安装的情形。
https://systemd.io/ || systemd

单用户/会话或临时设置[编辑 | 编辑源代码]

For some use cases it may be useful to change the time settings without touching the global system values. For example to test applications relying on the time during development or adjusting the system time zone when logging into a server remotely from another zone.

To make an application "see" a different date/time than the system one, you can use the faketime(1) utility (from libfaketime).

If instead you want an application to "see" a different time zone than the system one, set the TZ environment variable, for example:

$ date && export TZ=":/usr/share/zoneinfo/Pacific/Fiji" && date
Tue Nov  1 14:34:51 CET 2016
Wed Nov  2 01:34:51 FJT 2016

This is different than just setting the time, as for example it allows to test the behavior of a program with positive or negative UTC offset values, or the effects of DST changes when developing on systems in a non-DST time zone.

Another use case is having different time zones set for different users of the same system: this can be accomplished by setting the TZ variable in the shell's configuration file, see Environment variables#Defining variables.

提示和技巧[编辑 | 编辑源代码]

fake-hwclock[编辑 | 编辑源代码]

alarm-fake-hwclock designed especially for system without battery backed up RTC, it includes a systemd service which on shutdown saves the current time and on startup restores the saved time, thus avoiding strange time travel errors.

Install fake-hwclock-gitAUR, start/enable the service fake-hwclock.service.

虚拟 PTP[编辑 | 编辑源代码]

Virtual machine guests may obtain time from the host machine using the PTP (Precision Time Protocol) /dev/ptp0 interface. The interface is more accurate compared to using NTP over IP between the host and guest.

chrony and ntpd can each use the virtual-PTP device to sync the time between guest and host, by configuring the device as if it is a real PTP reference clock.

故障排除[编辑 | 编辑源代码]

时间显示的既不是 UTC 也不是本地时间[编辑 | 编辑源代码]

这可能是由多种原因造成的。假如说你的硬件时间使用的是 localtime,但 timedatectl被设为将其用作 UTC 时间,结果是你的时区偏移设置被应用了两次,导致系统显示的既不是UTC也不是本地时间。

要强制校准你的时钟,并且让系统写入正确 UTC 时间到硬件时钟,遵循下面几步:

  • 设置好 ntpd(不需要将其作为服务来运行);
  • 正确设置你的时区
  • 运行 ntpd -qg 以手动从网络同步时钟,此时在进行时间校正时,会忽略本地 UTC 时间与网络 UTC 时间之间过大的差异;
  • 运行 hwclock --systohc,将当前软件 UTC 时间写入硬件时间。

外部资源[编辑 | 编辑源代码]