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