Linux的计时系统(Timekeeping)在ULK中有专门一章来讲,但ULK第三版是基于2.6.10的,TimeKeeping部分已经有了很大的变化,如NOHZ mode和hrtimer,在ULK中都没有涉及,本文希望能对现在的kernel的Timekeeping部分做较完整的讲述。
从2.6.10到2.6.38,Timekeeping部分的变动可以参考这篇文章,里面比较详细的讲述了2.6.16之前版本的Timekeeping的不足,以及日后的改进,到2.6.38,文章里提到的改进大多都已经merge到kernel里。
如ULK所说,Timekeeping主要完成以下工作:
*更新自系统启动以来所经过的时间
*更新系统的时间和日期
*衡量进程的运行时间以及为进程分配时间片
*更新资源使用统计数
*检查软定时器是否到时
2.6.10 kernel的Timekeeping的结构图如下:
虚线右侧是硬件部分,不同Arch提供不一样的硬件,左侧是kernel。
为了完成Timekeeping的功能,硬件需要提供至少一种Clock source和Clock event source。Clock source只是一个简单的Counter,使能之后以固定的频率递增,kernel通过主动读取Counter可以判断时间的增加值,Clock event source是一个硬件Timer,设定后,可以以固定的频率产生中断,kernel可以在中断服务程序里完成上面所说的工作。理论上来说,只有Timer也可以维持系统的运行,但所有与时间相关的精度就仅限于Timer的产生频率。而Timer的中断频率一般不会很高,桌面系统常用100HZ~1000HZ,所以现在的系统Timer和Count都是必不可少的。
x86提供了多种Clock source和Clock event source。
Clock source有tsc, hpet, acpi_pm,clock event source有pit, hpet, local apic timer, acpi_pm timer等。这些硬件在ULK上均有描述。假如系统中存在多种Clock source和clock event source,kernel会以一定的规则来选择。
对于clock source,稳定且分辨率更高的会被选用,当然通过/sys/devices/system/clocksource/clocksource0/available_clocksouce可以手动选择clock source;
对于clock event source,稍微复杂一些,在uniprocessor系统中,优先选择支持One-shot模式且分辨率更高的,在SMP系统中,存在global event device和local event device。这两者的区别在ULK中有讲,global event device主要负责soft timer的处理,local event device负责与本CPU相关的工作,如CPU上进程执行时间的检查以及后面要说的hrtimer等。local event device除了优先选择oneshot模式和分辨率更高的之外,还要求device支持设置中断亲和力,因为local event device产生的中断,其他CPU是不能响应的。因此 lapic timer是local event device的最佳人选;local event device一般选择hpet或者pit(没有hpet的情况下)。
在我的机器上,global device是hpet,local device是lapic timer。
除了上面说的硬件之外,还有一个RTC,主要是为了保存实时时钟,但他也提供了timer的功能,只不过kernel很少用,定时开机会用到。
上述硬件的lapic产生的中断的中断号为0xef(LOC),RTC的中断号为0x8,global event device的中断号为0,即我们常说的timer中断。
Timer的中断在kernel即对应与jiffies,每次Timer中断,jiffies就增加1,为防止jiffies在短时间溢出,还有一个64位的jiffies_64,二者共用低32位。
kernel每收到一次Timer中断,就会将jiffies加1,并读取Clock source的值,通过HZ与s/ms/ns之间的关系计算系统的当前时间,写入到xtime和wall_to_monotonic中;检查进程的时间片是否用完,并为用完时间片的进程分配新的时间片;处理统计工作(Profiling);并在退出中断的时候检查soft timer是否到时。
soft timer采用的是时间轮模型,从上面的图可以知道,它是基于jiffies的,换句话说,他将jiffies作为其时钟源,当jiffies发生变化时,soft timer就会被处理,也就是global event device产生中断后。但对soft timer的处理并不是在timer 中断中进行的,而是在退出timer中断是的exit_irq()函数,该函数会检查softirq,soft timer就是以softirq的形式实现的。run_timer_softirq()函数执行具体的处理过程。
应该说,2.6.16之前的timekeeping是比较简单的,但弊端也较多。主要体现在一下几个方面:
1,缺乏必要的抽象层。代码与硬件结合紧密,不同架构下往往需要实现很多重复的代码,比如对clock source和clock event的管理
2,周期性的timer中断导致CPU不能进入sleep mode
3,soft timer的精度不够,其基于jiffies,最多只能提供jiffies的精度,在这样的timer模型下,要提高精度,只能提高HZ数,但这会给系统带来额外的负担
因此,2.6.16之后的kerel加入很多修改,主要包括:
*clock source和clock event source抽象层
*NOHZ mode
*hrtimer
clock source和clock event source抽象层将clock source device和clock event device的硬件进行抽象并统一管理,Arch相关的代码只需要负责注册这两类device即可;
NOHZ mode则抛弃了以前periodic的方式(周期性的产生Timer中断),使用硬件timer的oneshot模式,即配置一次,产生一次中断,这样就可以在系统空闲的时候,将硬件timer的下一次到期时间设置得较长,让CPU能够较长时间的进入sleep mode,从而达到省电和降温的目的;
hrtimer在传统的time wheel的模式外,新建立了一套timer机制,叫做hrtimers,即高精度定时器。hrtimer不再基于jiffies(Tick),最高可以达到ns的精度(需要硬件的支持)
新的timekeeping架构如下图:
在新的架构之下,硬件被抽象成为clock source device和clock event device,时钟中断被抽象称为clock event,event还以分发,不同的event device在收到event之后,会去执行其所属的event_handler,而event_handler是回调函数,由tick模块根据不同的mode进行填充。
tick模块中继续对event device进行包装,产生tick device的概念,由于NOHZ和hrtimer的引入,event device可以有两种工作模式:periodic,oneshot。具有工作模式属性的event device即为tick device。事实上,tick device仅包含event device和mode两个成员。
对应于event device的两种模式,tick模块实现了三种模式的tick,PERIODIC_MODE, NOHZ_MODE_LOWRES, NOHZ_MODE_HIGHRES。
在kernel的config中,有几个选项来控制这些模式的选择:
CONFIG_NO_HZ, CONFIG_HIGH_RES_TIMERS
当CONFIG_NO_HZ=yes,CONFIG_HIGH_RES_TIMERS=no,使用NOHZ_MODE_LOWRES模式
当CONFIG_HIGH_RES_TIMERS=yes,使用NOHZ_MODE_HIGHRES模式
当CONFIG_NO_HZ=no, CONFIG_HIGH_RES_TIMERS=no,使用PERIODIC_MODE
PERIODIC_MODE下的tick周期性的产生,频率为HZ的值,硬件timer也配置为周期性的产生中断;
NOHZ_MODE_LOWRES模式下,tick的产生不是周期性的,而根据当前系统的负载来决定下一次tick的产生时间,对于硬件来说就是使用ONESHOT mode,在每次timer产生时决定下一次中断产生的时间,再将计算得到的值写入到硬件timer的寄存器中;
NOHZ_MODE_HIGHRES模式跟NOHZ_MODE_LOWRES模式类似,但是NOHZ_MODE_LOWRES模式决定下一次tick产生的时间还是以tick_period为单位的,其精度并没有提升,而NOHZ_MODE_HIGHRES模式则通过判断最近到时的hrtimer的到期值来决定下一次tick产生的时间,因此精度可以达到ns。
因此高精度的hrtimer也只能在NOHZ_MODE_HIGHRES模式下使用。
从实现上来说,这三个模式的主要差别就是event device的event_handler是不同的,PERIODIC_MODE的event_handler是tick_handle_periodic(),NOHZ_MODE_LOWRES的event_handler是tick_nohz_handler(),NOHZ_MODE_HIGHRES的event_handler是hrtimer_interrupt()。
kernel在初始化时会首先进入PERIODIC_MODE,然后在event_handler中根据kernel的CONFIG和硬件是否支持决定是否进入NOHZ_MODE_LOWRES和NOHZ_MODE_HIGHRES。
前面说过基于时间轮的soft timer精度只能到达tick的精度,而hrtimer的精度则可以达到ns,其原因就在于hrtimer只能在NOHZ_MODE_HIGHRES模式下工作,而此模式下,下一次tick的产生是根据本地CPU中最早到期的hrtimer的到期时间决定的,因此下一次tick的产生精度也就能达到ns,而在下一次tick的event_handler中,hrtimer就会被处理。
时间轮的timer是在softirq中处理的,而hrtimer是在timer的中断处理函数中处理的。