clock source用於為linux內核提供一個時間基線,如果你用linux的date命令獲取當前時間,內核會讀取當前的clock source,轉換並返回合適的時間單位給用戶空間。在硬件層,它通常實現為一個由固定時鍾頻率驅動的計數器,計數器只能單調地增加,直到溢出為止。時鍾源是內核計時的基礎,系統啟動時,內核通過硬件RTC獲得當前時間,在這以后,在大多數情況下,內核通過選定的時鍾源更新實時時間信息(牆上時間),而不再讀取RTC的時間。本節的內核代碼樹基於V3.4.10。
/*****************************************************************************************************/
聲明:本博內容均由http://blog.csdn.net/droidphone原創,轉載請注明出處,謝謝!
/*****************************************************************************************************/
1. struct clocksource結構
內核用一個clocksource結構對真實的時鍾源進行軟件抽象,現在我們從clock source的數據結構開始,它的定義如下:
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:時鍾源的精度
- 1--99: 不適合於用作實際的時鍾源,只用於啟動過程或用於測試;
- 100--199:基本可用,可用作真實的時鍾源,但不推薦;
- 200--299:精度較好,可用作真實的時鍾源;
- 300--399:很好,精確的時鍾源;
- 400--499:理想的時鍾源,如有可能就必須選擇它作為時鍾源;
1.2 read回調函數
1.3 mult和shift字段
t = cycle/F;
t = (cycle * mult) >> shift;
F = (1 << shift) / mult;
static inline s64 clocksource_cyc2ns(cycle_t cycles, u32 mult, u32 shift)
{
return ((u64) cycles * mult) >> shift;
} 從轉換精度考慮,mult的值是越大越好,但是為了計算過程不發生溢出,mult的值又不能取得過大。為此內核假設cycle計數值被轉換后的最大時間值:10分鍾(600秒),主要的考慮是CPU進入IDLE狀態后,時間信息不會被更新,只要在10分鍾內退出IDLE,clocksource的cycle計數值就可以被正確地轉換為相應的時間,然后系統的時間信息可以被正確地更新。當然最后的結果不一定是10分鍾,它由clocksource_max_deferment進行計算,並保存max_idle_ns字段中,tickless的代碼要考慮這個值,以防止在NO_HZ配置環境下,系統保持IDLE狀態的時間過長。在這樣,由10分鍾這個假設的時間值,我們可以推算出合適的mult和shift值。
2. clocksource的注冊和初始化
由上圖可見,最終大部分工作會轉由__clocksource_register_scale完成,該函數首先完成對mult和shift值的計算,然后根據mult和shift值,最終通過clocksource_max_deferment獲得該clocksource可接受的最大IDLE時間,並記錄在clocksource的max_idle_ns字段中。clocksource_enqueue函數負責按clocksource的rating的大小,把該clocksource按順序掛在全局鏈表clocksource_list上,rating值越大,在鏈表上的位置越靠前。
3. clocksource watchdog
系統中可能同時會注冊對個clocksource,各個clocksource的精度和穩定性各不相同,為了篩選這些注冊的clocksource,內核啟用了一個定時器用於監控這些clocksource的性能,定時器的周期設為0.5秒:
#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是不穩定的,定時器的回調函數通過clocksource_watchdog_kthread線程標記該clocksource,並把它的rate修改為0,表示精度極差。
4. 建立clocksource的簡要過程
在系統的啟動階段,內核注冊了一個基於jiffies的clocksource,代碼位於kernel/time/jiffies.c:
struct clocksource clocksource_jiffies = {
.name = "jiffies",
.rating = 1, /* lowest valid rating*/
.read = jiffies_read,
.mask = 0xffffffff, /*32bits*/
.mult = NSEC_PER_JIFFY << JIFFIES_SHIFT, /* details above */
.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函數,它將返回該clocksource:
struct clocksource * __init __weak clocksource_default_clock(void)
{
return &clocksource_jiffies;
}
然后,在初始化的后段,clocksource的代碼會把全局變量curr_clocksource設置為上述的clocksource:
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,所以經過clocksource_select()函數后,curr_clocksource將會被設為最合適的clocksource。如果clocksource_select函數認為需要切換更好的時鍾源,它會通過timekeeping_notify通知timekeeping系統,使用新的clocksource進行時間計數和更新操作。
