Linux时间管理之clocksource

来源:本站
导读:目前正在解读《Linux时间管理之clocksource》的相关信息,《Linux时间管理之clocksource》是由用户自行发布的知识型内容!下面请观看由(电工技术网 - www.9ddd.net)用户发布《Linux时间管理之clocksource》的详细说明。

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

structclocksource{/**Hotpathdata,fitsinasinglecachelinewhenthe*clocksourceitselfiscachelinealigned.*/cycle_t(*read)(structclocksource*cs);cycle_tcycle_last;cycle_tmask;u32mult;u32shift;u64max_idle_ns;u32maxadj;#ifdefCONFIG_ARCH_CLOCKSOURCE_DATAstructarch_clocksource_dataarchdata;#endifconstchar*name;structlist_headlist; intrating;int(*enable)(structclocksource*cs);void(*disable)(structclocksource*cs);unsignedlongflags;void(*suspend)(structclocksource*cs);void(*resume)(structclocksource*cs);/*private:*/#ifdefCONFIG_CLOCKSOURCE_WATCHDOG/*Watchdogrelateddata,usedbytheframework*/structlist_headwd_list;cycle_tcs_last;cycle_twd_last;#endif}____cacheline_aligned;

这些参数当中,比较重要的是rating,shift,mult。其中rating在上一篇博文提到了:

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

我们基本在前面看到:

include/linux/acpi_pmtmr.h------------------------------------------#definePMTMR_TICKS_PER_SEC3579545drivers/clocksource/acpi_pm.c---------------------------------------------staticstructclocksourceclocksource_acpi_pm={.name="acpi_pm",.rating=200,.read=acpi_pm_read,.mask=(cycle_t)ACPI_PM_MASK,.mult=0,/*tobecalculated*/.shift=22,.flags=CLOCK_SOURCE_IS_CONTINUOUS,};dmesgoutput------------------------[0.664201]hpet0:8comparators,64-bit14.318180MHzcounterarch/86/kernel/hpet.c--------------------------------staticstructclocksourceclocksource_hpet={.name="hpet",.rating=250,.read=read_hpet,.mask=HPET_MASK,.flags=CLOCK_SOURCE_IS_CONTINUOUS,.resume=hpet_resume_counter,#ifdefCONFIG_X86_64.archdata={.vclock_mode=VCLOCK_HPET},#endif};dmesgoutput:-----------------------------[0.004000]Detected2127.727MHzprocessor.arch/x86/kernel/tsc.c--------------------------------------staticstructclocksourceclocksource_tsc={.name="tsc",.rating=300,.read=read_tsc,.resume=resume_tsc,.mask=CLOCKSOURCE_MASK(64),.flags=CLOCK_SOURCE_IS_CONTINUOUS|CLOCK_SOURCE_MUST_VERIFY,#ifdefCONFIG_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_clocksourcetschpetacpi_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时钟为例:

staticstructclocksourceclocksource_tsc={.name="tsc",.rating=300,.read=read_tsc,.resume=resume_tsc,.mask=CLOCKSOURCE_MASK(64),.flags=CLOCK_SOURCE_IS_CONTINUOUS|CLOCK_SOURCE_MUST_VERIFY,#ifdefCONFIG_X86_64.archdata={.vclock_mode=VCLOCK_TSC},#endif};/*---------arch/x86/kernel/tsc.c-------------------*/staticcycle_tread_tsc(structclocksource*cs){cycle_tret=(cycle_t)get_cycles();returnret>=clocksource_tsc.cycle_last?ret:clocksource_tsc.cycle_last;}/*-------arch/x86/include/asm/tsc.h----------------------*/staticinlinecycles_tget_cycles(void){unsignedlonglongret=0;#ifndefCONFIG_X86_TSCif(!cpu_has_tsc) return0;#endifrdtscll(ret);returnret;}/*------arch/x86/include/asm/msr.h-----------------*/#definerdtscll(val)((val)=__native_read_tsc())static__always_inlineunsignedlonglong__native_read_tsc(void){DECLARE_ARGS(val,low,high);asmvolatile("rdtsc":EAX_EDX_RET(val,low,high));returnEAX_EDX_VAL(val,low,high);}

根据这个脉络,我们知道,最终就是rdtsc这条指令来获取当前计数值cycles。

扯了半天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-convertsclocksourcecyclestonanoseconds*@cycles:cycles*@mult:cycletonanosecondmultiplier*@shift:cycletonanosecondpisor(poweroftwo)**Convertscyclestonanoseconds,usingthegivenmultandshift.**XXX-Thiscouldusesomemult_lxl_ll()asmoptimization*/staticinlines64clocksource_cyc2ns(cycle_tcycles,u32mult,u32shift){return((u64)cycles*mult)>>shift;}

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

void__clocksource_updatefreq_scale(structclocksource*cs,u32scale,u32freq){u64sec;/**Calcthemaximumnumberofsecondswhichwecanrunbefore*wrappingaround.Forclocksourceswhichhaveamask>32bit *weneedtolimitthemaxsleeptimetohaveagood*conversionprecision.10minutesisstillareasonable*amount.Thatresultsinashiftvalueof24fora*clocksourcewithmask>=40bitandf>=4GHz.Thatmapsto*~0.06ppmgranularityforNTP.Weapplythesame12.5% *marginaswedoinclocksource_max_deferment()*/sec=(cs->mask-(cs->mask>>3));do_p(sec,freq);do_p(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);/**forclocksourcesthathavelargemults,toavoidoverflow.*Sincemultmaybeadjustedbyntp,addansafetyextramargin**/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>typedefunsignedintu32;typedefunsignedlonglongu64;#defineNSEC_PER_SEC1000000000Lvoidclocks_calc_mult_shift(u32*mult,u32*shift,u32from,u32to,u32maxsec){u64tmp;u32sft,sftacc=32;/***Calculatetheshiftfactorwhichislimitingtheconversion**range:**/tmp=((u64)maxsec*from)>>32;while(tmp){tmp>>=1;sftacc--;}/***Findtheconversionshift/multpairwhichhasthebest**accuracyandfitsthemaxsecconversionrange:**/for(sft=32;sft>0;sft--){tmp=(u64)to<<sft;tmp+=from/2;//do_p(tmp,from);tmptmp=tmp/from;if((tmp>>sftacc)==0)break;}*mult=tmp;*shift=sft;}intmain(){u32tsc_mult;u32tsc_shift;u32tsc_frequency=2127727000/1000;//TSCfrequency(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=%dshift=%dn",tsc_mult,tsc_shift);return0;}

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

mult=7885042shift=24root@manu:~/code/c/self/time#pythonPython2.7.3(default,Apr102013,05:46:21)[GCC4.6.3]onlinux2Type"help","copyright","credits"or"license"formoreinformation.>>>(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(structclocksource*cs,u32scale,u32freq){/*Initializemult/shiftandmax_idle_ns*/__clocksource_updatefreq_scale(cs,scale,freq);/*Addclocksourcetotheclcoksourcelist*/mutex_lock(&clocksource_mutex);clocksource_enqueue(cs);clocksource_enqueue_watchdog(cs);clocksource_select();mutex_unlock(&clocksource_mutex);return0;}

第一函数是__clocksource_updatefreq_scale,计算shift,mult还有max_idle_ns,前面讲过了。

clocksource_enqueue是将clocksource链入全局链表,根据的是rating,rating高的放前面。

clocksource_select会选择最好的clocksource记录在全局变量curr_clocksource,同时会通知timekeeping,切换最好的clocksource会有内核log:

manu@manu:~$dmesg|grepSwitching[0.673002]Switchingtoclocksourcehpet[1.720643]Switchingtoclocksourcetsc

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

#defineWATCHDOG_INTERVAL(HZ>>1)#defineWATCHDOG_THRESHOLD(NSEC_PER_SEC>>4)

如果0.5秒内,误差大于0.0625s,表示这个clocksource精度极差,将rating设成0。

提醒:《Linux时间管理之clocksource》最后刷新时间 2024-03-14 01:06:30,本站为公益型个人网站,仅供个人学习和记录信息,不进行任何商业性质的盈利。如果内容、图片资源失效或内容涉及侵权,请反馈至,我们会及时处理。本站只保证内容的可读性,无法保证真实性,《Linux时间管理之clocksource》该内容的真实性请自行鉴别。