Linux时间管理之clocksource(1)(2)
筒子比较关心的问题是如何计算,精度如何,其实我不太喜欢这种计算,Kernel总是因为某些原因把代码写的很蛋疼。反正揣摩代码意图要花不少时间,收益嘛其实也不太大.如何实现我也不解释了,我以TSC为例子我评估下这种mult+shift的精度。
- #include<stdio.h>
- #include<stdlib.h>
- typedef unsigned int u32;
- typedef unsigned long long u64;
- #define NSEC_PER_SEC 1000000000L
- void
- clocks_calc_mult_shift(u32 *mult, u32 *shift, u32 from, u32 to, u32 maxsec)
- {
- u64 tmp;
- u32 sft, sftacc= 32;
- /*
- * * Calculate the shift factor which is limiting the conversion
- * * range:
- * */
- tmp = ((u64)maxsec * from) >> 32;
- while (tmp) {
- tmp >>=1;
- sftacc--;
- }
- /*
- * * Find the conversion shift/mult pair which has the best
- * * accuracy and fits the maxsec conversion range:
- * */
- 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;
- }
- int main()
- {
- 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_khz
- fprintf(stderr,"mult = %d shift = %d\n",tsc_mult,tsc_shift);
- return 0;
- }
600是根据TSC clocksource的MASK算出来的的入参,感兴趣可以自己推算看下结果:
- mult = 7885042 shift = 24
- root@manu:~/code/c/self/time# python
- Python 2.7.3 (default, Apr 10 2013, 05:46:21)
- [GCC 4.6.3] on linux2
- Type "help", "copyright", "credits" or "license" for more information.
- >>> (2127727000*7885042)>>24
- 1000000045L
- >>>
我们知道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_hz
- TSC (arch/x86/kernel/tsc.c)
- ----------------------------------------
- device_initcall(init_tsc_clocksource);
- init_tsc_clocksource
- |_____clocksource_register_khz
- ACPI_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/shift and max_idle_ns */
- __clocksource_updatefreq_scale(cs, scale, freq);
- /* Add clocksource to the 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。
评论暂时关闭