clock source用于為linux內(nèi)核提供一個(gè)時(shí)間基線,如果你用linux的date命令獲取當(dāng)前時(shí)間,內(nèi)核會(huì)讀取當(dāng)前的clock source,轉(zhuǎn)換并返回合適的時(shí)間單位給用戶空間。在硬件層,它通常實(shí)現(xiàn)為一個(gè)由固定時(shí)鐘頻率驅(qū)動(dòng)的計(jì)數(shù)器,計(jì)數(shù)器只能單調(diào)地增加,直到溢出為止。時(shí)鐘源是內(nèi)核計(jì)時(shí)的基礎(chǔ),系統(tǒng)啟動(dòng)時(shí),內(nèi)核通過硬件RTC獲得當(dāng)前時(shí)間,在這以后,在大多數(shù)情況下,內(nèi)核通過選定的時(shí)鐘源更新實(shí)時(shí)時(shí)間信息(墻上時(shí)間),而不再讀取RTC的時(shí)間。本節(jié)的內(nèi)核代碼樹基于V3.4.10。
1. ?struct clocksource結(jié)構(gòu)
內(nèi)核用一個(gè)clocksource結(jié)構(gòu)對(duì)真實(shí)的時(shí)鐘源進(jìn)行軟件抽象,現(xiàn)在我們從clock source的數(shù)據(jù)結(jié)構(gòu)開始,它的定義如下:
[cpp]?view plain?copy
struct?clocksource?{??
/*?
*?Hotpath?data,?fits?in?a?single?cache?line?when?the?
*?clocksource?itself?is?cacheline?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_DATA??
struct?arch_clocksource_data?archdata;??
#endif??
const?char?*name;??
struct?list_head?list;??
int?rating;??
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;??
我們只關(guān)注clocksource中的幾個(gè)重要的字段。
1.1 ?rating:時(shí)鐘源的精度
同一個(gè)設(shè)備下,可以有多個(gè)時(shí)鐘源,每個(gè)時(shí)鐘源的精度由驅(qū)動(dòng)它的時(shí)鐘頻率決定,比如一個(gè)由10MHz時(shí)鐘驅(qū)動(dòng)的時(shí)鐘源,他的精度就是100nS。clocksource結(jié)構(gòu)中有一個(gè)rating字段,代表著該時(shí)鐘源的精度范圍,它的取值范圍如下:
1--99: 不適合于用作實(shí)際的時(shí)鐘源,只用于啟動(dòng)過程或用于測(cè)試;
100--199:基本可用,可用作真實(shí)的時(shí)鐘源,但不推薦;
200--299:精度較好,可用作真實(shí)的時(shí)鐘源;
300--399:很好,精確的時(shí)鐘源;
400--499:理想的時(shí)鐘源,如有可能就必須選擇它作為時(shí)鐘源;
1.2 ?read回調(diào)函數(shù)
時(shí)鐘源本身不會(huì)產(chǎn)生中斷,要獲得時(shí)鐘源的當(dāng)前計(jì)數(shù),只能通過主動(dòng)調(diào)用它的read回調(diào)函數(shù)來獲得當(dāng)前的計(jì)數(shù)值,注意這里只能獲得計(jì)數(shù)值,也就是所謂的cycle,要獲得相應(yīng)的時(shí)間,必須要借助clocksource的mult和shift字段進(jìn)行轉(zhuǎn)換計(jì)算。
1.3 ?mult和shift字段
因?yàn)閺腸locksource中讀到的值是一個(gè)cycle計(jì)數(shù)值,要轉(zhuǎn)換為時(shí)間,我們必須要知道驅(qū)動(dòng)clocksource的時(shí)鐘頻率F,一個(gè)簡單的計(jì)算就可以完成:
t = cycle/F;
可是clocksource并沒有保存時(shí)鐘的頻率F,因?yàn)槭褂蒙厦娴墓竭M(jìn)行計(jì)算,需要使用浮點(diǎn)運(yùn)算,這在內(nèi)核中是不允許的,因此,內(nèi)核使用了另外一個(gè)變通的辦法,根據(jù)時(shí)鐘的頻率和期望的精度,事先計(jì)算出兩個(gè)輔助常數(shù)mult和shift,然后使用以下公式進(jìn)行cycle和t的轉(zhuǎn)換:
t = (cycle * mult) >> shift;
只要我們保證:
F = (1 << shift) / mult;
內(nèi)核內(nèi)部使用64位進(jìn)行該轉(zhuǎn)換計(jì)算:
[cpp]?view plain?copy
static?inline?s64?clocksource_cyc2ns(cycle_t?cycles,?u32?mult,?u32?shift)??
{??
return?((u64)?cycles?*?mult)?>>?shift;??
}??
從轉(zhuǎn)換精度考慮,mult的值是越大越好,但是為了計(jì)算過程不發(fā)生溢出,mult的值又不能取得過大。為此內(nèi)核假設(shè)cycle計(jì)數(shù)值被轉(zhuǎn)換后的最大時(shí)間值:10分鐘(600秒),主要的考慮是CPU進(jìn)入IDLE狀態(tài)后,時(shí)間信息不會(huì)被更新,只要在10分鐘內(nèi)退出IDLE,clocksource的cycle計(jì)數(shù)值就可以被正確地轉(zhuǎn)換為相應(yīng)的時(shí)間,然后系統(tǒng)的時(shí)間信息可以被正確地更新。當(dāng)然最后的結(jié)果不一定是10分鐘,它由clocksource_max_deferment進(jìn)行計(jì)算,并保存max_idle_ns字段中,tickless的代碼要考慮這個(gè)值,以防止在NO_HZ配置環(huán)境下,系統(tǒng)保持IDLE狀態(tài)的時(shí)間過長。在這樣,由10分鐘這個(gè)假設(shè)的時(shí)間值,我們可以推算出合適的mult和shift值。
2. ?clocksource的注冊(cè)和初始化
通常,clocksource要在初始化階段通過clocksource_register_hz函數(shù)通知內(nèi)核它的工作時(shí)鐘的頻率,調(diào)用的過程如下:
由上圖可見,最終大部分工作會(huì)轉(zhuǎn)由__clocksource_register_scale完成,該函數(shù)首先完成對(duì)mult和shift值的計(jì)算,然后根據(jù)mult和shift值,最終通過clocksource_max_deferment獲得該clocksource可接受的最大IDLE時(shí)間,并記錄在clocksource的max_idle_ns字段中。clocksource_enqueue函數(shù)負(fù)責(zé)按clocksource的rating的大小,把該clocksource按順序掛在全局鏈表clocksource_list上,rating值越大,在鏈表上的位置越靠前。
每次新的clocksource注冊(cè)進(jìn)來,都會(huì)觸發(fā)clocksource_select函數(shù)被調(diào)用,它按照rating值選擇最好的clocksource,并記錄在全局變量curr_clocksource中,然后通過timekeeping_notify函數(shù)通知timekeeping,當(dāng)前clocksource已經(jīng)變更,關(guān)于timekeeping,我將會(huì)在后續(xù)的博文中闡述。
3. ?clocksource watchdog
系統(tǒng)中可能同時(shí)會(huì)注冊(cè)對(duì)個(gè)clocksource,各個(gè)clocksource的精度和穩(wěn)定性各不相同,為了篩選這些注冊(cè)的clocksource,內(nèi)核啟用了一個(gè)定時(shí)器用于監(jiān)控這些clocksource的性能,定時(shí)器的周期設(shè)為0.5秒:
[cpp]?view plain?copy
#define?WATCHDOG_INTERVAL?(HZ?>>?1)??
#define?WATCHDOG_THRESHOLD?(NSEC_PER_SEC?>>?4)??
當(dāng)有新的clocksource被注冊(cè)時(shí),除了會(huì)掛在全局鏈表clocksource_list外,還會(huì)同時(shí)掛在一個(gè)watchdog鏈表上:watchdog_list。定時(shí)器周期性地(0.5秒)檢查watchdog_list上的clocksource,WATCHDOG_THRESHOLD的值定義為0.0625秒,如果在0.5秒內(nèi),clocksource的偏差大于這個(gè)值就表示這個(gè)clocksource是不穩(wěn)定的,定時(shí)器的回調(diào)函數(shù)通過clocksource_watchdog_kthread線程標(biāo)記該clocksource,并把它的rate修改為0,表示精度極差。
4. ?建立clocksource的簡要過程
在系統(tǒng)的啟動(dòng)階段,內(nèi)核注冊(cè)了一個(gè)基于jiffies的clocksource,代碼位于kernel/time/jiffies.c:
[cpp]?view plain?copy
struct?clocksource?clocksource_jiffies?=?{??
.name???????=?"jiffies",??
.rating?????=?1,?/*?lowest?valid?rating*/??
.read???????=?jiffies_read,??
.mask???????=?0xffffffff,?/*32bits*/??
.mult???????=?NSEC_PER_JIFFY?<
.shift??????=?JIFFIES_SHIFT,??
};??
......??
static?int?__init?init_jiffies_clocksource(void)??
{??
return?clocksource_register(&clocksource_jiffies);??
}??
core_initcall(init_jiffies_clocksource);??
它的精度只有1/HZ秒,rating值為1,如果平臺(tái)的代碼沒有提供定制的clocksource_default_clock函數(shù),它將返回該clocksource:
[cpp]?view plain?copy
struct?clocksource?*?__init?__weak?clocksource_default_clock(void)??
{??
return?&clocksource_jiffies;??
}??
然后,在初始化的后段,clocksource的代碼會(huì)把全局變量curr_clocksource設(shè)置為上述的clocksource:
[cpp]?view plain?copy
static?int?__init?clocksource_done_booting(void)??
{??
......??
curr_clocksource?=?clocksource_default_clock();??
......??
finished_booting?=?1;??
......??
clocksource_select();??
......??
return?0;??
}??
fs_initcall(clocksource_done_booting);??
當(dāng)然,如果平臺(tái)級(jí)的代碼在初始化時(shí)也會(huì)注冊(cè)真正的硬件clocksource,所以經(jīng)過clocksource_select()函數(shù)后,curr_clocksource將會(huì)被設(shè)為最合適的clocksource。如果clocksource_select函數(shù)認(rèn)為需要切換更好的時(shí)鐘源,它會(huì)通過timekeeping_notify通知timekeeping系統(tǒng),使用新的clocksource進(jìn)行時(shí)間計(jì)數(shù)和更新操作。
?
評(píng)論