Linux时间管理之clocksource

http://blog.chinaunix.net/uid-24774106-id-3909829.html

前面提到了Linux下的时间相关的硬件。TSC PIT,HPET,ACPI_PM,这些硬件以一定的频率产生时钟中断,来帮助我们计时。Linux为了管理这些硬件,抽象出来clocksource。

    struct clocksource{/**Hotpath data,fitsina single cache line when the*clocksource itselfiscacheline aligned.*/cycle_t(*read)(struct clocksource*cs);cycle_t cycle_last;cycle_t mask;u32 mult;u32 shift;u64 max_idle_ns;u32 maxadj;#ifdef CONFIG_ARCH_CLOCKSOURCE_DATAstruct arch_clocksource_data archdata;#endifconstchar*name;struct list_head list;intrating;int(*enable)(struct clocksource*cs);void(*disable)(struct clocksource*cs);unsigned long flags;void(*suspend)(struct clocksource*cs);void(*resume)(struct clocksource*cs);/*private:*/#ifdef CONFIG_CLOCKSOURCE_WATCHDOG/*Watchdog related data,used by the framework*/struct list_head wd_list;cycle_t cs_last;cycle_t wd_last;#endif}____cacheline_aligned;

1–99: 不适合于用作实际的时钟源,只用于启动过程或用于测试;100–199:基本可用,可用作真实的时钟源,但不推荐;200–299:精度较好,可用作真实的时钟源;300–399:很好,精确的时钟源;400–499:理想的时钟源,如有可能就必须选择它作为时钟源; 我们基本在前面看到:

    include/linux/acpi_pmtmr.h——————————————#define PMTMR_TICKS_PER_SEC 3579545drivers/clocksource/acpi_pm.c———————————————static struct clocksource clocksource_acpi_pm={.name="acpi_pm",.rating=200,.read=acpi_pm_read,.mask=(cycle_t)ACPI_PM_MASK,.mult=0,/*tobe calculated*/.shift=22,.flags=CLOCK_SOURCE_IS_CONTINUOUS,};dmesg output————————[0.664201]hpet0:8 comparators,64-bit 14.318180 MHz counterarch/86/kernel/hpet.c——————————–static struct clocksource clocksource_hpet={.name="hpet",.rating=250,.read=read_hpet,.mask=HPET_MASK,.flags=CLOCK_SOURCE_IS_CONTINUOUS,.resume=hpet_resume_counter,#ifdef CONFIG_X86_64.archdata={.vclock_mode=VCLOCK_HPET},#endif};dmesg output:—————————–[0.004000]Detected 2127.727 MHz processor.arch/x86/kernel/tsc.c————————————–static struct clocksource clocksource_tsc={.name="tsc",.rating=300,.read=read_tsc,.resume=resume_tsc,.mask=CLOCKSOURCE_MASK(64),.flags=CLOCK_SOURCE_IS_CONTINUOUS|CLOCK_SOURCE_MUST_VERIFY,#ifdef CONFIG_X86_64.archdata={.vclock_mode=VCLOCK_TSC},#endif};

从上面可以看到,acpi_pm,hpet tsc的rating分别是200,250,300,他们的rating基本是和他们的frequency符合,TSC以2127.727MHz的频率技压群雄,等级rating=300最高,被选择成current_clocksource:

    root@manu:~#cat /sys/devices/system/clocksource/clocksource0/available_clocksourcetsc hpet acpi_pmroot@manu:~#cat /sys/devices/system/clocksource/clocksource0/current_clocksourcetsc

除此外,还有两个参数shift和mult,这两个参数是干啥的呢? 我们想一下,假如我们需要给你个以一定频率输出中断的硬件,你如何计时?比如我有一个频率是1000Hz的硬件,当前时钟源计数是3500,过了一段时间,我抬头看了下时钟源计数至是5500,过去了2000cycles,我就知道了过去了2000/1000 =2 second。

    times_elapse =cycles_interval/frequency

从上面的例子中,我抬头看了下当前计数值这个肯定是瞎掰了,实际上要想获取时钟源还是需要和硬件打交道的。在clocksource中有一个成员变量是read,这个就是一个时钟源注册的时候,提供的一个函数,如果你想获得我的当前计数值,请调用这个read 函数。以TSC时钟为例:

    static struct clocksource clocksource_tsc={.name="tsc",.rating=300,.read=read_tsc,.resume=resume_tsc,.mask=CLOCKSOURCE_MASK(64),.flags=CLOCK_SOURCE_IS_CONTINUOUS|CLOCK_SOURCE_MUST_VERIFY,#ifdef CONFIG_X86_64.archdata={.vclock_mode=VCLOCK_TSC},#endif};/*———arch/x86/kernel/tsc.c——————-*/static cycle_tread_tsc(struct clocksource*cs){cycle_t ret=(cycle_t)get_cycles();return ret>=clocksource_tsc.cycle_last?ret:clocksource_tsc.cycle_last;}/*——-arch/x86/include/asm/tsc.h———————-*/static inline cycles_tget_cycles(void){unsigned long long ret=0;#ifndef CONFIG_X86_TSCif(!cpu_has_tsc)return 0;#endifrdtscll(ret);return ret;}/*——arch/x86/include/asm/msr.h—————–*/#definerdtscll(val)\((val)=__native_read_tsc())static __always_inline unsigned long long__native_read_tsc(void){DECLARE_ARGS(val,low,high);asm volatile("rdtsc":EAX_EDX_RET(val,low,high));return EAX_EDX_VAL(val,low,high);}

根据这个脉络,我们知道,最终就是rdtsc这条指令来获取当前计数值cycles。rdtsc这条指令我前面有有博文介绍摸我。 扯了半天read这个成员变量,可以回到shift和mult了。其实shift和mult是为了解决下面这个公式的:

    times_elapse=cycles_interval / frequency

就像上面的公式,有频率就足以计时了。为啥弄出来个shift和mult。原因在于kernel搞个除法不太方便,必须转化乘法和移位。Kernel中有很多这种把除法转化成乘法的样例。那么公式变成了:

    times_elapse=cycles_interval*mult>>shift

Kernel用乘法+移位来替换除法:根据cycles来计算过去了多少ns。

    /***clocksource_cyc2ns-converts clocksource cyclestonanoseconds*@cycles:cycles*@mult:cycletonanosecond multiplier*@shift:cycletonanosecond divisor(power of two)**Converts cyclestonanoseconds,using the given multandshift.**XXX-This could use some mult_lxl_ll()asm optimization*/static inline s64 clocksource_cyc2ns(cycle_t cycles,u32 mult,u32 shift){return((u64)cycles*mult)>>shift;}

单纯从精度上讲,肯定是mult越大越好,但是计算过程可能溢出,所以mult也不能无限制的大,这个计算中有个magic number600 :

    void __clocksource_updatefreq_scale(struct clocksource*cs,u32 scale,u32 freq){u64 sec;/**Calc the maximum number of seconds which we can run before*wrapping around.Forclocksources which have a mask>32bit*we needtolimit the max sleeptimetohave a good*conversion precision.10 minutesisstill a reasonable*amount.That resultsina shift value of 24fora*clocksource with mask>=40bitandf>=4GHz.That mapsto*~0.06ppm granularityforNTP.We apply the same 12.5%*margin as wedoinclocksource_max_deferment()*/sec=(cs->mask-(cs->mask>>3));do_div(sec,freq);do_div(sec,scale);if(!sec)sec=1;elseif(sec>600&&cs->mask>UINT_MAX)sec=600;clocks_calc_mult_shift(&cs->mult,&cs->shift,freq, NSEC_PER_SEC/scale,sec*scale);/**forclocksources that have large mults,toavoid overflow.*Since mult may be adjusted by ntp,add an safety extra margin**/cs->maxadj=clocksource_max_adjustment(cs);while((cs->mult+cs->maxadj<cs->mult)||(cs->mult-cs->maxadj>cs->mult)){cs->mult>>=1;cs->shift–;cs->maxadj=clocksource_max_adjustment(cs);}cs->max_idle_ns=clocksource_max_deferment(cs);}

这个600的意思是600秒,表示的Timer两次计算当前计数值的差不会超过10分钟。主要考虑的是系统进入IDLE状态之后,时间信息不会被更新,10分钟内只要退出IDLE,clocksource还是可以成功的转换时间。当然了,最后的这个时间不一定就是10分钟,它由clocksource_max_deferment计算并将结果存储在max_idle_ns中. 筒子比较关心的问题是如何计算 ,精度如何,其实我不太喜欢这种计算,Kernel总是因为某些原因把代码写的很蛋疼.反正揣摩代码意图要花不少时间,收益嘛其实也不太大.如何实现我也不解释了,我以TSC为例子我评估下这种mult+shift的精度.

    #include<stdio.h>#include<stdlib.h>typedef unsignedintu32;typedef unsigned long long u64;#define NSEC_PER_SEC 1000000000Lvoidclocks_calc_mult_shift(u32*mult,u32*shift,u32 from,u32to,u32 maxsec){u64 tmp;u32 sft,sftacc=32;/***Calculate the shift factor whichislimiting the conversion**range:**/tmp=((u64)maxsec*from)>>32;while(tmp){tmp>>=1;sftacc–;}/***Find the conversion shift/mult pair which has the best**accuracyandfits the maxsec conversion range:**/for(sft=32;sft>0;sft–){tmp=(u64)to<<sft;tmp+=from/2;//do_div(tmp,from);tmp=tmp/from;if((tmp>>sftacc)==0)break;}*mult=tmp;*shift=sft;}intmain(){u32 tsc_mult;u32 tsc_shift;u32 tsc_frequency=2127727000/1000; //TSC frequency(KHz)clocks_calc_mult_shift(&tsc_mult,&tsc_shift,tsc_frequency,NSEC_PER_SEC/1000,600*1000); //NSEC_PER_SEC/1000是因为TSC的注册是clocksource_register_khzfprintf(stderr,"mult = %d shift = %d\n",tsc_mult,tsc_shift);return 0;}

600是根据TSC clocksource的MASK算出来的的入参,感兴趣可以自己推算看下结果:

    mult =7885042shift =24root@manu:~/code/c/self/time# pythonPython 2.7.3 (default, Apr 10 2013, 05:46:21)[GCC 4.6.3] on linux2Type "help", "copyright", "credits" or "license" for more information.>>> (2127727000*7885042)>>241000000045L>>>

我们知道TSC的frequency是2127727000Hz,如果cycle走过2127727000,就意味过去了1秒,或者说10^9(us).按照我们的算法得出的时间是1000000045us. 这个误差是多大呢,每走10^9秒,误差是45秒,换句话说,运行257天,产生1秒的计算误差.考虑到NTP的存在,这个运算精度还可以了. 接下来是注册和各大clocksource PK. 各大clocksource会调用clocksource_register_khz或者clocksource_register_hz来注册.

    HPET(arch/x86/kernel/hpet)—————————————-hpet_enable|_____hpet_clocksource_register|_____clocksource_register_hzTSC (arch/x86/kernel/tsc.c)—————————————-device_initcall(init_tsc_clocksource);init_tsc_clocksource|_____clocksource_register_khzACPI_PM(drivers/cloclsource/acpi_pm.c)——————————————-fs_initcall(init_acpi_pm_clocksource);init_acpi_pm_clocksource|_____clocksource_register_hz

最终都会调用__clocksource_register_scale.

    int__clocksource_register_scale(struct clocksource*cs,u32 scale,u32 freq){/*Initialize mult/shiftandmax_idle_ns*/__clocksource_updatefreq_scale(cs,scale,freq);/*Add clocksourcetothe clcoksource list*/mutex_lock(&clocksource_mutex);clocksource_enqueue(cs);clocksource_enqueue_watchdog(cs);clocksource_select();mutex_unlock(&clocksource_mutex);return 0;}

第一函数是__clocksource_updatefreq_scale,计算shift,mult还有max_idle_ns,前面讲过了. clocksource_enqueue是将clocksource链入全局链表.根据的是rating,rating高的放前面. clocksource_select会选择最好的clocksource记录在全局变量curr_clocksource,同时会通知timekeeping,切换最好的clocksource会有内核log:

    manu@manu:~$dmesg|grep Switching[0.673002]Switching to clocksource hpet[1.720643]Switching to clocksource tsc

clocksource_enqueue_watchdog会将clocksource挂到watchdog链表.watchdog顾名思义,监控所有clocksource:

    #define WATCHDOG_INTERVAL(HZ>>1)#define WATCHDOG_THRESHOLD(NSEC_PER_SEC>>4)

如果0.5秒内,误差大于0.0625s,表示这个clocksource精度极差,将rating设成0. 总算可以睡觉了.亲下我家小宝宝 去睡觉.参考文献:1Linux时间子系统之一:clock source(时钟源)2 Linux 3.4.61 source code.擒龙要下海,打虎要上山。

Linux时间管理之clocksource

相关文章:

你感兴趣的文章:

标签云: