UNIX时间是在运行UNIX的计算机上测量的。
这个答案将期望您知道什么是协调世界时(UTC),国际原子时(TAI)和SI秒。对它们的解释远远超出了Unix和Linux Stack Exchange的范围。 这不是物理或天文学堆栈交换。
硬体
您的计算机包含用于驱动时钟和计时器的各种振荡器。确切的说,它的结构因计算机而异。但是通常,并且非常笼统地说:
- 有一个可编程的间隔计时器某个地方(PIT),可以对其进行编程以计算给定的振荡次数,并向中央处理单元触发中断。
- 中央处理器上有一个周期计数器,对于执行的每个指令周期,该计数器仅计数1。
广义上的操作理论
操作系统内核利用PIT来生成滴答声。它将PIT设置为自由运行,在例如百分之一秒的时间间隔内计算正确的振荡次数,生成中断,然后自动将计数重置为再次运行。对此有一些变化,但是从本质上讲,这会导致滴答中断以固定的频率引发。
在软件中,内核每隔一个滴答就增加一个计数器。它知道节拍频率,因为它首先对PIT进行了编程。因此,它知道每秒有多少滴答声。它可以使用它来知道何时增加一个计数秒的计数器。后者是内核的“ UNIX Time”思想。实际上,如果将其留给自己的设备,它确实会以每SI秒一个的速率向上计数。
有四件事使这变得复杂,我将以非常笼统的术语来介绍。
硬件并不完美。一个PIT,其数据表中说它的振荡器频率为N Hertz ,它的频率可能为(例如)N .00002 Hertz,具有明显的后果。
该方案与电源管理的互操作性非常差,因为CPU每秒醒来数百次,要做的只是增加变量中的数字。因此,某些操作系统具有所谓的“不滴答”设计。内核不(让PIT为每个滴答发送中断),而是(从低级调度程序中)计算出要执行多少滴答,而没有线程量子用完,然后对PIT进行编程以将这么多滴答计数到线程中。发出滴答中断之前的未来。它知道然后必须记录N的通过在下一个滴答中断中滴答,而不是1个滴答。
应用程序软件可以更改内核的当前时间。它可以步进值或压摆值。回转涉及到调整秒数以增加秒计数器。因此,无论如何,秒计数器不一定以秒为单位,即使假设完美的振荡器。步进涉及简单地在秒计数器中写入一个新数字,这通常要等到最后一秒被滴答后的1 SI秒才会发生。
现代内核不仅数秒,而且数秒。但是,每纳秒一次的滴答中断很荒谬,而且通常是完全不可行的。这就是诸如周期计数器之类的东西发挥作用的地方。内核会记住每秒(或每个刻度)的周期计数器值,并可以从当前值算出在某些人想知道时间(以纳秒为单位)时计数器值算出,自最后一秒(或蜱)。但是,由于指令周期频率可以改变,因此电源和热量管理也遭受了严重破坏,因此内核执行诸如依赖诸如高精度事件计时器(HPET)之类的附加硬件的事情。
C语言和POSIX
C语言的标准库描述了不透明的类型,方面时time_t
,结构类型tm
与各种指定字段,和不同的库函数等time()
,mktime()
和localtime()
。
简而言之:C语言本身仅保证它time_t
是可用的数字数据类型之一,并且计算时间差的唯一可靠方法是difftime()
函数。POSIX标准实际上提供了更严格的保证,它time_t
是整数类型之一,并且自从Epoch开始计数秒数。也是指定timespec
结构类型的POSIX标准。
该time()
功能有时被称为系统调用。实际上,如今,很长一段时间以来,它并不是许多系统上的底层系统调用。例如,在FreeBSD上,基础系统调用为clock_gettime()
,它具有各种可用的“时钟”,以各种方式以秒或秒+纳秒为单位进行测量。应用程序软件通过该系统调用从内核读取UNIX Time。(匹配的clock_settime()
系统调用使他们可以踩踏,而adjtime()
系统调用则允许他们将其压摆。)
许多人对POSIX标准提出了非常明确和确切的要求。这样的人常常不实际阅读 POSIX标准。按照其原理,计数“自纪元以来的秒数”(即标准使用的短语)的想法有意未指定POSIX秒与SI秒的长度相同,也未指定POSIX秒的长度为gmtime()
“ UTC,尽管出现了”。POSIX标准是故意的足够松散,因此它允许(例如)管理员使用的UNIX系统,并通过在发生后一周重新设置时钟来手动修正leap秒调整。的确,原理表明,它有意放松得足以容纳故意将时钟设置为不同于当前UTC时间的某个时间的系统。
UTC和TAI
从内核获得的UNIX时间解释取决于应用程序中运行的库例程。POSIX在内核的时间和中的“中断时间”之间指定一个标识struct tm
。但是,正如丹尼尔·J·伯恩斯坦(Daniel J. “在违约中比在遵守方面更值得荣誉”是一个容易想到的短语。
确实是这样。如今,有几种系统基于由亚瑟·戴维·奥尔森(Arthur David Olson)编写的库例程进行解释,该例程参考了臭名昭著的“奥尔森时区数据库”,通常将其编码为/usr/share/zoneinfo/
。Olson系统具有两种模式:
- 内核的“自纪元以来的秒数”被认为是自1970-01-01 00:00:00 UTC以来的UTC秒,leap秒除外。这将使用
posix/
Olson时区数据库文件集。整天都有86400内核秒,而一分钟永远不会有61秒,但是它们并不总是SI的长度,发生when秒时内核时钟需要回转或步进。
- 内核的“自纪元以来的秒数”被视为计算自1970-01-01 00:00:10 TAI以来的TAI秒。这将使用
right/
Olson时区数据库文件集。内核秒数为1 SI秒长,内核时钟无需调整或步进即可调整leap秒,但细分时间可以具有23:59:60之类的值,而天数不一定总是86400内核秒数。
M. Bernstein编写了包括他的daemontools
工具集在内的一些工具,这是必需的,right/
因为time_t
自1970-01-01 00:00:00 TAI以来,他们只增加了10 来获得TAI秒。他在手册页中对此进行了记录。
(也许在不知不觉中)此要求由诸如daemontools-encore
和runit
和Felix von Leitner's的工具集继承libowfat
。使用伯恩斯坦multilog
,冈特multilog
,或佩普svlogd
与奥尔森posix/
结构中,例如,所有的TAI64N时间戳将(在写这篇的时间)的后面26秒实际 TAI因为1970-01-01 00:00:10第二计数TAI。
我和Laurent Bercot在s6和nosh中解决了这个问题,尽管我们采取了不同的方法。M. Bercot tai_from_sysclock()
依赖于编译时标志。使用TAI64N处理的nosh工具会查看TZ
和TZDIR
环境变量以进行自动检测posix/
,right/
如果可以的话。
有趣的是,FreeBSD文档time2posix()
和posix2time()
函数允许使用相当于TAI秒的Olson right/
模式time_t
。但是,显然没有启用它们。
再来一次…
UNIX时间是通过计算机硬件中包含的振荡器在运行UNIX的计算机上测量的。它不使用SI秒;即使从表面上看,它也不是UTC。并且故意使您的时钟出错。
进一步阅读