筒子比较关心的问题是如何计算,精度如何,其实我不太喜欢这种计算,Kernel总是因为某些原因把代码写的很蛋疼。反正揣摩代码意图要花不少时间,收益嘛其实也不太大.如何实现我也不解释了,我以TSC为例子我评估下这种mult+shift的精度。

  1. #include<stdio.h> 
  2. #include<stdlib.h> 
  3.   
  4. typedef unsigned int u32; 
  5. typedef unsigned long long u64; 
  6.   
  7. #define NSEC_PER_SEC 1000000000L 
  8.   
  9. void 
  10. clocks_calc_mult_shift(u32 *mult, u32 *shift, u32 from, u32 to, u32 maxsec) 
  11.     u64 tmp; 
  12.     u32 sft, sftacc32
  13.   
  14.     /* 
  15.      * * Calculate the shift factor which is limiting the conversion 
  16.      * * range: 
  17.      * */ 
  18.     tmp = ((u64)maxsec * from) >> 32; 
  19.     while (tmp) { 
  20.             tmp >>=1; 
  21.             sftacc--; 
  22.         } 
  23.   
  24.     /* 
  25.      * * Find the conversion shift/mult pair which has the best 
  26.      * * accuracy and fits the maxsec conversion range: 
  27.      * */ 
  28.     for (sft = 32; sft > 0; sft--) { 
  29.             tmp = (u64) to << sft
  30.             tmp += from / 2; 
  31.             //do_p(tmp, from); 
  32.             tmptmp = tmp/from; 
  33.             if ((tmp >> sftacc) == 0) 
  34.                 break; 
  35.         } 
  36.     *mult = tmp
  37.     *shift = sft
  38.   
  39.   
  40. int main() 
  41. {  
  42.     u32 tsc_mult; 
  43.     u32 tsc_shift ; 
  44.   
  45.     u32 tsc_frequency = 2127727000/1000; //TSC frequency(KHz) 
  46.     clocks_calc_mult_shift(&tsc_mult,&tsc_shift,tsc_frequency,NSEC_PER_SEC/1000,600*1000); //NSEC_PER_SEC/1000是因为TSC的注册是clocksource_register_khz 
  47.   
  48.     fprintf(stderr,"mult = %d shift = %d\n",tsc_mult,tsc_shift); 
  49.     return 0; 

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

  1. mult = 7885042 shift = 24 
  2. root@manu:~/code/c/self/time# python 
  3. Python 2.7.3 (default, Apr 10 2013, 05:46:21)  
  4. [GCC 4.6.3] on linux2 
  5. Type "help", "copyright", "credits" or "license" for more information. 
  6. >>> (2127727000*7885042)>>24 
  7. 1000000045L 
  8. >>>  

我们知道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来注册。

  1. HPET (arch/x86/kernel/hpet) 
  2. ---------------------------------------- 
  3. hpet_enable 
  4. |_____hpet_clocksource_register 
  5.            |_____clocksource_register_hz 
  6.   
  7. TSC  (arch/x86/kernel/tsc.c) 
  8. ---------------------------------------- 
  9. device_initcall(init_tsc_clocksource); 
  10.   
  11. init_tsc_clocksource 
  12. |_____clocksource_register_khz 
  13.   
  14.   
  15. ACPI_PM(drivers/cloclsource/acpi_pm.c) 
  16. ------------------------------------------- 
  17. fs_initcall(init_acpi_pm_clocksource); 
  18.   
  19. init_acpi_pm_clocksource 
  20. |_____clocksource_register_hz 

最终都会调用__clocksource_register_scale. 

  1. int __clocksource_register_scale(struct clocksource *cs, u32 scale, u32 freq) 
  2.   
  3.     /* Initialize mult/shift and max_idle_ns */ 
  4.     __clocksource_updatefreq_scale(cs, scale, freq); 
  5.   
  6.     /* Add clocksource to the clcoksource list */ 
  7.     mutex_lock(&clocksource_mutex); 
  8.     clocksource_enqueue(cs); 
  9.     clocksource_enqueue_watchdog(cs); 
  10.     clocksource_select(); 
  11.     mutex_unlock(&clocksource_mutex); 
  12.     return 0; 

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

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

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

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

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

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

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


相关内容