对 Linux 时间的理解一直很零散、片面,这次下决心系统的整理下。

    在 x86 体系结构上,总体来说,存在两种与时间相关的硬件:时钟和定时器。前者相当于手表,记录绝对时间,后者相当于秒表,记录相对时间。但其实两者都可归结为定时器。以下就分别说开来。

    先说简单的吧:时钟,也称为实时时钟,Real Time Clock(RTC)。在所有的 PC 上都存在,在硬件上,它独立于 CPU 和其它所有芯片,是和 CMOS 集成在一起的,依靠主板上的微型电池供电。这就是为什么系统关机后,时间也不会受到影响。RTC 能在 IRQ8 上发出周期性的中断,频率在 2-8192HZ 之间。此外,就像手表也可以用来记时一样,RTC 也支持对其进行编程,以使当 RTC 到达某个特定的值时激活 IRQ8 总线,即把它当成秒表来使用。当然,Linux 只利用它来获取时间和日期的,在系统启动的时候,内核通过读取 RTC 来初始化 xtime 变量,再结合系统启动时间(用啥来表示待会会提到),就能计算出当前时间。

    其实对于 RTC 的理解,以上就足够了。后续待补充的,是 Linux 内核初始化及更新 xtime 的具体实现。

    接下来就谈谈相对复杂的定时器。

    跟时钟不同,定时器关注的是相对时间。每当设定的时间到了,定时器就会跳出来提醒你,就如同每天早上叫你起床的闹钟。有两点,第一,设定的时间到了,也即周期性的时间,是如何实现的?第二,如何跳出来?

    在 x86 体系结构中有多种定时器:

    1. TSC(Time Stamp Counter)

    利用 64 位的时间戳计数器寄存器来实现,接收外部振荡器的时钟信号,在每个时钟信号到来时加 1。所谓的时钟信号,是指有固定周期并与运行无关的信号量,对应于 CPU 的主频。例如当 CPU 的主频为 1GHz,那么 TSC 每纳秒增加 1 次。

    可以看到,该计数器的增长是非常快的,那是否会产生溢出呢?算算便知:假设 CPU 主频为 10G,2^63/10^10,结果约为 29 年,足够用了。

    利用该寄存器,可以获得非常精确的时间测量。事实上,Linux 也是据此来确定 CPU 的主频。calibrate_tsc() 函数通过计算一个大约在 5ms 的时间间隔内所产生的时钟信号的个数,来算出 CPU 的实际频率。因为编译内核时并不会声明该频率,所以同一内核映象可以运行在任何时钟频率的 CPU 上。

    通过汇编指令 rdtsc 可以读取该寄存器的值。所谓性能应该是计算出来的而不是测试出来的,该指令提供了一种计算的途径,后面会专门介绍。

    2. PIT(Programmable Interval Timer)

    每个 IBM 兼容 PC 都至少包含一个 PIT。如果觉得很枯燥、记忆起来有点困难的话,那就记住这个 Programmable,可编程。这个很关键。谁会去对它编程呢?当然是伟大的 Linux 内核了。好,前戏谈完,即将正式进入主题了。

    首先,既然是间隔定时器,那间隔是多少?如何确定下来的呢?答案叫 HZ,也叫 tick rate。Linux 内核依据这个值,对该定时器编程。在 Linux 2.5 之后,HZ 从 100 调整到了 1000。这个 1/HZ 的间隔也成为 tick,翻译成中文叫节拍或者滴答。说到这里,你会不会有困惑:1/1000 秒也才 1ms,这个精度也太差了吧,像 us、ns 之类的精度,依靠这个定时器,怎么能达到呢。这个问题很好,但我们暂时先不回答。

    其次,如何通知定时的时间已到。估计你也猜到了,答案是中断。PIT 使用的中断号是 IRQ0。

    有了以上两点,我们就能知道,PIT 永远以内核确定的固定频率,不停的发出中断。所谓的系统定时器,指的就是 PIT。而这个中断,就是大名鼎鼎的时钟中断,看看中断里都干了啥事情,就知道其地位了:

    1. 更新系统运行时间

    2. 更新实际时间

    3. 检查当前进程是否已经耗尽了时间片,如果是,则重新调度

    4. 在 smp 系统上,均衡调度程序中各处理器上的运行队列

    是不是都是些高大上的事情?

    对于 HZ 应该设置成多少的问题,显然,越高越精确,但是系统的负载也越重。结论是:

    1. 没有证据显示,对系统中所有程序而言,频率为 1000 比频率为 100 更合适;

    2. 在现代计算机系统上,时钟频率为 1000HZ 不会导致难以接受的负担,并且不会对系统性能造成较大的影响。

    这些说完后,就可以引出 jiffies 这个内核中的全局变量了。jiffies 记录的是系统启动以来产生的节拍总数,也即时钟中断的总数。因为一秒钟内时钟中断的次数等于 HZ,所以 jiffies 一秒内的增加值也就为 HZ。所以,有以下转换公式:

    秒 = jiffies / HZ

    或者:

    jiffies = 秒 * HZ

    话说,除了了解概念之外,还必须理解、必须使用过,方能真正掌握。对于 PIT、HZ、tick 等等,应该是理解了,但是 jiffies 是用来干嘛的、什么地方能用到呢?