clock source顧名思義就是提供給系統提供時鐘的時鐘源。 clock source負責讀取芯片中按時間增加的值(所謂cycle),并提供給timekeeper,當然也要提供按cycle的值計算時間間隔的內容。 clocksource以及timer相關的內容都在kernel/kernel/time目錄下面。
obj-y += timekeeping.o ntp.o clocksource.o jiffies.o timer_list.oobj-y += timeconv.o posix-clock.o alarmtimer.oobj-$(CONFIG_GENERIC_CLOCKEVENTS_BUILD) += clockevents.oobj-$(CONFIG_GENERIC_CLOCKEVENTS) += tick-common.oobj-$(CONFIG_GENERIC_CLOCKEVENTS_BROADCAST) += tick-broadcast.o tick-broadcast-hrtimer.oobj-$(CONFIG_GENERIC_SCHED_CLOCK) += sched_clock.oobj-$(CONFIG_TICK_ONESHOT) += tick-oneshot.oobj-$(CONFIG_TICK_ONESHOT) += tick-sched.oobj-$(CONFIG_TIMER_STATS) += timer_stats.o下面來看一下時鐘源是怎么注冊上去,怎么提供計算時間的內容給timekeeper。
linux可以有很多時鐘源,其中一種時鐘源是jiffies。還有就是平臺相關的時鐘源,精度較高。 當然在注冊了很多種時鐘源之后,linux內核也用某種方式去選擇當前的時鐘源來保證最好的精度。
jiffies我們知道就是一秒鐘會增加相當于HZ大小的一個變量。 首先需要根據時鐘源,填充clocksource相關數據結構中的成員。
static struct clocksource clocksource_jiffies = { .name= "jiffies", .rating= 1, .read= jiffies_read, /*讀取當前jiffies*/ .mask= 0xffffffff, /*32bits*/ .mult= NSEC_PER_JIFFY << JIFFIES_SHIFT, /* details above */ .shift= JIFFIES_SHIFT,};從這個例子,可以看出來 (mult>>shift)*cloclsource->read() 這個讀的就是jiffies數。 當然如果要注冊一些更高精度的時鐘源,mult和shift要進行小心的計算,以保證其值不會溢出。這個后面再看。
kernel用乘法+移位來替換除法:根據cycles來計算過去了多少ns。 單純從精度上講,肯定是mult越大越好,但是計算過程可能溢出,所以mult也不能無限制的大,這個計算中有個magic number 600 : 這個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 unsigned int u32;typedef unsigned long long u64;#define NSEC_PER_SEC 1000000000Lvoidclocks_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_div(tmp, from); tmp = 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 f600是根據TSC clocksource的MASK算出來的的入參,感興趣可以自己推算看下結果:mult = 7885042 shift = 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).按照我們的算法得出的時間是1000000045ns. 這個誤差是多大呢,每走10^9秒,誤差是45納秒,換句話說,運行257天,產生1秒的計算誤差.考慮到NTP的存在,這個運算精度還可以了.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.
以高通msm8916為例,除了jiffies,其他clocksource是沒有直接使用kernel/kernel/time/clocksource.c文件里邊的接口的。 其他arm_arch_timer.c[/kernel/driver/clocksource/arm_arch_timer.c] 和 arch_timer.c [kernel/arch/arm/kernel/arch_timer.c] 兩個都是使用 sched_clock.c[/kernel/kernel/time/sched_clock.c]文件里邊的 sched_clock_register()來進行注冊,并使用sched_clock_32()函數讀取當前時間。
新聞熱點
疑難解答