在您计算机深处的某个地方,通常在硬件中,有一个时钟振荡器以某个频率 f 运行。出于本示例的目的,假设它以 1 kHz 或每秒 1,000 个周期运行。设置好振荡器的每个周期都会触发 CPU 中断。
还有一个低级计数器c。每次触发时钟中断时,操作系统都会递增计数器。目前我们假设它会增加 1,尽管在实践中通常不会出现这种情况。
操作系统还会在计数器增加时检查它的值。当c 等于 1,000 时,这意味着正好一秒钟过去了。此时操作系统做了两件事:
- 它增加另一个计数器变量,该变量以秒为单位跟踪一天中的实际时间。我们将调用这个其他计数器
t。 (这将是一个很大的数字,所以它至少是一个 32 位的变量,或者现在,如果可能的话,是 64 位。)
- 它将
c 重置为0。
最后,当您调用time() 时,内核会简单地返回t 的当前值。真的很简单!
嗯,实际上,它比这要复杂一些。我忽略了最初设置计数器 t 的值的细节,以及操作系统如何确保振荡器以正确的频率运行,以及其他一些事情。
当操作系统启动时,如果它位于 PC、工作站、大型机或其他“大型”计算机上,它通常有一个电池供电的实时时钟,可用于设置 t 的初始值。 (如果我们谈论的 CPU 是嵌入式微控制器,另一方面,它可能没有任何类型的时钟,所有这些都没有实际意义,time() 根本没有实现。)
此外,当您(以 root 身份)调用 settimeofday 时,您基本上只是提供了一个值来塞入内核的 t 计数器。
当然,在联网系统上,诸如 NTP 之类的东西正忙于使系统的时间保持最新。
NTP 可以通过两种方式做到这一点:
- 如果它注意到
t 已经偏离,它可以将其设置为一个新值,或多或少就像 settimeofday() 所做的那样。
- 如果它注意到
t 只是稍微偏离了一点,或者如果它注意到底层振荡器没有以完全正确的频率计数,它可以尝试调整该频率。
调整频率听起来很简单,但细节可能会变得相当复杂。您可以想象,底层振荡器的频率 f 略有调整。或者,您可以想象 f 保持不变,但是当时间中断触发时,添加到 c 的数字增量会略有调整。
特别是,内核通常不会在每次定时器中断时给c 加1,而当c 达到1,000 时,就表示一秒钟过去了。内核更有可能在每次定时器中断时将像 1,000,000 这样的数字添加到 c,这意味着它会等到 c 达到 1,000,000,000 后再决定一秒钟过去了。这样,内核可以对时钟频率进行更细粒度的调整:如果运行速度有点慢,它可以改变主意,在每个定时器中断时将 1,000,001 加到c,这样就可以运行了只是 tiny 快一点。 (大约是百万分之一,您可以很容易地看到。)
我忽略的另一件事是time() 不是询问系统时间的唯一方法。您还可以拨打 gettimeofday() 之类的电话,它会为您提供以秒 + 微秒 (struct timeval) 表示的亚秒级时间戳,或 clock_gettime(),它会为您提供以秒 + 纳秒 ( struct timespec)。这些是如何实施的?好吧,内核不仅可以读取t 的值,还可以查看c 以查看下一秒的距离。特别是,如果c 的计数达到 1,000,000,000,那么内核可以通过将c 除以 1,000 为您提供微秒,并且可以通过直接返回 c 为您提供纳秒。
两个脚注:
(1) 如果我们调整了频率,并且在每个低级计时器滴答声中将 1,000,001 加到 c 上,c 通常不会准确地达到 1,000,000,000,因此在决定是否增加时进行测试t 必须涉及大于或等于条件,我们必须从 c 中减去 1,000,000,000,而不仅仅是清除它。换句话说,代码看起来像
if(c >= 1000000000) {
t++;
c -= 1000000000;
}
(2) 由于time() 和gettimeofday() 是最简单的两个系统调用,并且由于调用它们的程序可能(根据定义)对系统调用开销引起的任何延迟特别敏感,因此这些调用最有可能基于vDSO 机制实现,如果它正在使用的话。