本文是一篇译文,原文地址:http://sameerseth-tcp-ip.blogspot.hk/2010/09/kernel-ticks-jiffies-and-high.html
关于内核中ticks 和 jiffies的解释已经有很多了,其中又属“understanding Linux kernel” --- O‘reilly 一书中的说明堪称典范,在此,我仅仅就一些现有情况做个总结。
在Linux系统中,‘jiffies’ OR ‘ticks’是最好的时间分辨率。内核或用户空间中的 sleep/poll/select/timers等功能,都使用jiffies作为时间单位。例如,在内核或用户空间中可以阻塞的最小时间是1 jiffies。这些又意味着什么呢?
所有的事件,如sleep/poll/timers 等,都需要有一个在指定时间到期之后进行唤醒的通知机制。具体来说,如果一个进程说它需要在sleep OR poll 1秒钟后被唤醒,内核就会为这个事件注册一个定时器。定时器将在1秒钟之后到期,然后由回调函数将进程从wait-queue移出并加入run-queue中,这样一来,该进程就可以在未来的某个时间点上被调度执行。所有的等待事件都是通过这种方式被内核转换成了定时器事件。那么,你可以指定的内核通知的最短时间是什么,这个时间又是如何测量的呢?
我们可以要求内核进行通知的最短时间是1‘jiffy’。‘jiffy’是一个在“时钟中断处理程序“中周期性自增的计数器。“时钟中断处理程序”以一个预先定义的频率周期性运行,而时钟中断的速率或频率就被定义为“HZ”,它说明了1秒钟内产生的时钟中断次数。早期这个值被设置为100,后来在X86系统上又改为250。
100HZ意味着在1秒钟内将会产生100次时钟中断,周期为1/100秒,或(1000/100)10ms。这实际上是在说'jiffy'每10ms增加一次,或者可以得到的最高时间分辨率为10ms。相对于高速CPU来说,这或许是一个较长的时间。如果我们增加了HZ的值,就可以获得更高的时间分辨率,但同时CPU每秒钟将需要花费更多的时间来处理时钟中断。因此,需要在高的时间分辨率和处理时钟中断所花费的时间比例上进行权衡。在高速的CPU上这不应是一个问题。
我们如何将jiffies和内核的通知事件(定时器)联系起来呢?正如我们已经讨论过的,内核已经按照‘number of jiffies’将sleep/poll/timers等转换为了通知事件(定时器),它最小可以是从现在开始的1jiffy。那么这些内核定时器又是何时被处理的呢?
内核定时器将在时钟中断发生的时候被处理。因此,如果我们注册了一个将在1jiffy之后到期的定时器,那么我们将会在下个时钟中断到来的时候被唤醒。假设在一种场景下我们需要在1ms之后被唤醒,又假设在另一种场景下我们需要在5ms之后被唤醒。在前一个场景中,我们可能在1ms之后就被唤醒,或者也有可能最大需要10ms才能被唤醒,这取决于时钟中断是即将要发生还是刚刚才发生过。在后一个场景中也面临着同样的问题,我们可能在5ms时就被唤醒,或者最大需要10ms后才能被唤醒。一个经典的例子就是RTT的计算。在RTT低于1jiffy的情况下,我们仍将看到最小值1jiffy,或者如果刚巧在时钟中断之前传输报文,并在时钟中断之后得到ACK,我们仍会看到RTT为1jiffy,而事实上只有几个微秒(micro second)。在需要考虑系统时间精度的情况下,这些都会产生严重的影响。从某种程度上来说,所有上述的问题都可以通过增加时钟频率(HZ)来解决,因此,当前的X86系统上将时钟频率调整为250(在autoconf.h文件中将CONFIG_HZ设置为250),这意味着每4ms产生一次时钟中断。在高端系统上,这个值被设置到了1000,即1jiffy为1ms。
通常,我们看到的HZ定义如下:
include/*/param.h #ifdef __KERNEL__ # define HZ CONFIG_HZ /* Internal kernel timer frequency */ # define USER_HZ 100 /* some user interfaces are */ # define CLOCKS_PER_SEC (USER_HZ) /* in "ticks" like times() */ #endif
下面提供一些实现:
有两个变量 jiffies 和 jiffies_64。jiffies_64是一个64-bit的值,而jiffies是一个long型值。在32位系统中,jiffies是32位值而jiffies_64是64位值。保留两个值的原因在于,在较高的时钟中断频率下,32位的jiffies值将会更快的发生“回绕”。事实上,jiffies_64的低32位值在内核的许多地方被采用,这是因为在很多情况下32位值已经能够满足我们对时间精度的要求,并且在32位架构下访问64位的值将要花费多于一次的内存访问。
include/linux/jiffies.h extern u64 __jiffy_data jiffies_64; extern unsigned long volatile __jiffy_data jiffies;
jiffies_64的值在时钟中断的do_timer()调用中进行增加:
void do_timer(unsigned long ticks) { jiffies_64 += ticks; .... }
那么,jiffies的值又是在哪里增加的呢?我们没有明确的对jiffies进行增加。‘jiffies’是jiffies_64的低32位,并且我们可以在链接文件中看到如下内容:
arch/x86/kernel/vmlinux.lds.S #ifdef CONFIG_X86_32 OUTPUT_ARCH(i386) ENTRY(phys_startup_32) jiffies = jiffies_64;
在SMP机器上又是什么样的情况呢?
在SMP机器上,每个CPU都有时钟中断。同时,这些时钟中断将在各CPU上独立的发生。那么,又该如何处理jiffies呢?
在SMP机器上,只有一个CPU负责增加jiffies的值,其他的CPU将会像平常一样覆盖时钟中断的完整功能。哪一个CPU最早到达tick_setup_device()函数,就进行jiffies的管理工作:
kernel/time/tick-common.c static void tick_setup_device(struct tick_device *td, ....) { ... if (tick_do_timer_cpu == TICK_DO_TIMER_BOOT) { tick_do_timer_cpu = cpu; .... } .... }
如上,tick_do_timer_cpu会把将要进行jiffies管理的CPU ID存储起来。这个值将在tick_periodic()函数中被拿来与当前CPU ID进行比较,如果当前CPU就是被选中的CPU,则会调用do_timer()函数。类似的,在tick_sched_timer()函数中将会调用tick_do_update_jiffies64()来更新jiffies_64的值。
什么是高精度定时器?
当内核和硬件支持纳秒级的高精度定时器时,并不推荐每几个纳秒就产生一次周期性时钟中断,因为这将使CPU花费大量时间来处理时钟中断,从而拉低系统性能。为了避免这种情况,硬件(时钟中断)被设定为按需产生。它将依赖于下一个未决的定时器事件,如果它将在几个纳秒之后发生,我们就设定时钟中断在那时发生,否则,就让它在默认的时间后发生(10 ticks,如果ticks是1ms的话,就是在10ms之后)。check tick_do_update_jiffies64()由tick_sched_timer()调用。所有的高精度定时器代码都通过CONFIG_HIGH_RES_TIMERS宏来编译。