在我可以访问的 Raspberry Pi 和 Ubuntu 16.04 x86_64 机器上,运行
# date -s @0
回报
date: cannot set date: Invalid argument
thu 1 jan 1970 01:00:00 CET
我想知道:
- 不可能背后的原因是什么,
- 是否有一种编程方式将系统时间重置为 Epoch。
答案1
我无法在旧系统上准确地重现“无效参数”,但是:
# strace date -s '@-1'
…
clock_settime(CLOCK_REALTIME, {4294967295, 0}) = -1 EINVAL (Invalid argument)
settimeofday({4294967295, 0}, NULL) = -1 EINVAL (Invalid argument)
write(2, "date: ", 6date: ) = 6
write(2, "cannot set date", 15cannot set date) = 15
write(2, ": Invalid argument", 18: Invalid argument) = 18
write(2, "\n", 1
(然后date
继续显示它想要设置的日期,即使它实际上尚未设置。)
用简单的英语来说,date
就是调用内核将日期设置为纪元之前的一秒,内核告诉它建议的日期无效。
现在,在上面的跟踪中,您会看到秒数为 4294967295,而不是 -1。这个数字是 2^32-1,我在 32 位机器上运行它。这实际上是不是问题的根源:这是 中的显示问题strace
,它不知道该值是否应该被签名。事实上,秒数是一个有符号整数类型time_t
。 Linux 上的 Glibc 2.23 定义time_t
为__time_t
in /usr/include/time.h
、__time_t
as 、 as 以及__TIME_T_TYPE
中/usr/include/bits/types.h
__TIME_T_TYPE
的64__SYSCALL_SLONG_TYPE
位有符号类型。在内核方面,从 4.4 开始,调用参数类型的定义是__SQUAD_TYPE
/usr/include/bits/typesizes.h
__SQUAD_TYPE
/usr/include/bits/types.h
settimeofday
struct timespec
其中秒是__kernel_time_t
这是 64 位机器上的有符号 64 位类型,或者time64_t
这是 32 位机器上的有符号 64 位类型。所以内核确实将 -1 视为 -1 而不是某个大的正数。
你无法回到纪元之前的原因是 Linux 内核明确拒绝这样做。这settimeofday
系统调用如果返回 EINVAL(“无效参数”)timeval_valid
拒绝给定的时间结构,并且该功能拒绝负数秒数。
不同版本的内核具有不同组织的代码,但我认为行为没有改变。
我看不出有什么理由会拒绝正好为 0 的时间,并且它在我测试的虚拟机中有效。
进行此健全性检查的原因大概是有代码将自纪元以来的时间存储为无符号整数,因此无法处理纪元之前的日期。某些代码可能会将 0 解释为“日期未知”,因此拒绝 0 也是有意义的,但 Linux 实际上并没有这样做,而且它只会持续一秒钟。
纪元之前的时代当然可以被再现和操纵。许多软件需要跟踪过去事件的时间。几乎每个操作系统使用的时区数据库都包含可追溯到时区编纂时(铁路开始使用时)的历史数据。但你不能告诉 Linux 计算机当前的时间是1970年之前。
答案2
如果当前时区的纪元概念早于 (UTC) Unix 纪元,则可能会发生这种情况。这样的时代是无法代表的。