Linux時間子系統(tǒng)之:時鐘源
clock source 用于為 Linux 內(nèi)核提供一個時間基線,如果你用 Linux 的 date 命令獲取當(dāng)前時間,內(nèi)核會讀取當(dāng)前的 clock source,轉(zhuǎn)換并返回合適的時間單位給用戶空間。
在硬件層,它通常實現(xiàn)為一個由固定時鐘頻率驅(qū)動的計數(shù)器,計數(shù)器只能單調(diào)地增加,直到溢出為止。時鐘源是內(nèi)核計時的基礎(chǔ),系統(tǒng)啟動時,內(nèi)核通過硬件RTC獲得當(dāng)前時間,在這以后,在大多數(shù)情況下,內(nèi)核通過選定的時鐘源更新實時時間信息(墻上時間),而不再讀取RTC的時間。
本節(jié)的內(nèi)核代碼樹基于V3.4.10。
struct clocksource結(jié)構(gòu)
內(nèi)核用一個clocksource結(jié)構(gòu)對真實的時鐘源進(jìn)行軟件抽象,現(xiàn)在我們從clock source的數(shù)據(jù)結(jié)構(gòu)開始,它的定義如下:
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中的幾個重要的字段。
1 rating:時鐘源的精度
同一個設(shè)備下,可以有多個時鐘源,每個時鐘源的精度由驅(qū)動它的時鐘頻率決定,比如一個由10MHz時鐘驅(qū)動的時鐘源,他的精度就是100nS。clocksource結(jié)構(gòu)中有一個rating字段,代表著該時鐘源的精度范圍,它的取值范圍如下:
1--99:不適合于用作實際的時鐘源,只用于啟動過程或用于測試; 100--199:基本可用,可用作真實的時鐘源,但不推薦; 200--299:精度較好,可用作真實的時鐘源; 300--399:很好,精確的時鐘源; 400--499:理想的時鐘源,如有可能就必須選擇它作為時鐘源;
1.2 read回調(diào)函數(shù)
時鐘源本身不會產(chǎn)生中斷,要獲得時鐘源的當(dāng)前計數(shù),只能通過主動調(diào)用它的read回調(diào)函數(shù)來獲得當(dāng)前的計數(shù)值,注意這里只能獲得計數(shù)值,也就是所謂的cycle,要獲得相應(yīng)的時間,必須要借助clocksource的mult和shift字段進(jìn)行轉(zhuǎn)換計算。
1.3 mult和shift字段
因為從clocksource中讀到的值是一個cycle計數(shù)值,要轉(zhuǎn)換為時間,我們必須要知道驅(qū)動clocksource的時鐘頻率F,一個簡單的計算就可以完成:
t = cycle / F;
可是clocksource并沒有保存時鐘的頻率F,因為使用上面的公式進(jìn)行計算,需要使用浮點運算,這在內(nèi)核中是不允許的。
因此,內(nèi)核使用了另外一個變通的辦法,根據(jù)時鐘的頻率和期望的精度,事先計算出兩個輔助常數(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)換計算:
static inline s64
clocksource_cyc2ns(cycle_t cycles, u32 mult, u32 shift)
{
return ((u64) cycles * mult) >> shift;
}
從轉(zhuǎn)換精度考慮,mult 的值是越大越好,但是為了計算過程不發(fā)生溢出,mult 的值又不能取得過大。為此內(nèi)核假設(shè) cycle 計數(shù)值被轉(zhuǎn)換后的最大時間值:10分鐘(600秒),主要的考慮是 CPU 進(jìn)入 IDLE 狀態(tài)后,時間信息不會被更新,只要在10分鐘內(nèi)退出IDLE,clocksource 的 cycle 計數(shù)值就可以被正確地轉(zhuǎn)換為相應(yīng)的時間,然后系統(tǒng)的時間信息可以被正確地更新。
當(dāng)然最后的結(jié)果不一定是10分鐘,它由 clocksource_max_deferment 進(jìn)行計算,并保存 max_idle_ns 字段中,tickless 的代碼要考慮這個值,以防止在 NO_HZ 配置環(huán)境下,系統(tǒng)保持 IDLE 狀態(tài)的時間過長。在這樣,由 10 分鐘這個假設(shè)的時間值,我們可以推算出合適的 mult 和 shift 值。
2. clocksource的注冊和初始化
通常,clocksource要在初始化階段通過 clocksource_register_hz 函數(shù)通知內(nèi)核它的工作時鐘的頻率,調(diào)用的過程如下:

由上圖可見,最終大部分工作會轉(zhuǎn)由 __clocksource_register_scale 完成,該函數(shù)首先完成對 mult 和 shift 值的計算,然后根據(jù) mult 和 shift 值,最終通過 clocksource_max_deferment 獲得該 clocksource 可接受的最大 IDLE 時間,并記錄在 clocksource 的 max_idle_ns 字段中。clocksource_enqueue 函數(shù)負(fù)責(zé)按 clocksource 的 rating 的大小,把該 clocksource 按順序掛在全局鏈表 clocksource_list 上,rating 值越大,在鏈表上的位置越靠前。
每次新的 clocksource 注冊進(jìn)來,都會觸發(fā) clocksource_select 函數(shù)被調(diào)用,它按照 rating 值選擇最好的 clocksource,并記錄在全局變量 curr_clocksource 中,然后通過 timekeeping_notify 函數(shù)通知 timekeeping,當(dāng)前clocksource 已經(jīng)變更,關(guān)于 timekeeping,我將會在后續(xù)的博文中闡述。
3. clocksource watchdog
系統(tǒng)中可能同時會注冊對個 clocksource,各個 clocksource 的精度和穩(wěn)定性各不相同,為了篩選這些注冊的 clocksource,內(nèi)核啟用了一個定時器用于監(jiān)控這些 clocksource 的性能,定時器的周期設(shè)為0.5秒:
#define WATCHDOG_INTERVAL (HZ >> 1)
#define WATCHDOG_THRESHOLD (NSEC_PER_SEC >> 4)
當(dāng)有新的 clocksource 被注冊時,除了會掛在全局鏈表 clocksource_list 外,還會同時掛在一個 watchdog 鏈表上:watchdog_list。
定時器周期性地(0.5秒)檢查 watchdog_list 上的 clocksource,WATCHDOG_THRESHOLD 的值定義為0.0625秒。如果在0.5秒內(nèi),clocksource 的偏差大于這個值就表示這個 clocksource 是不穩(wěn)定的,定時器的回調(diào)函數(shù)通過 clocksource_watchdog_kthread 線程標(biāo)記該 clocksource,并把它的 rate 修改為0,表示精度極差。
4. 建立clocksource的簡要過程
在系統(tǒng)的啟動階段,內(nèi)核注冊了一個基于 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 函數(shù),它將返回該 clocksource:
struct clocksource * __init __weak clocksource_default_clock(void)
{
return &clocksource_jiffies;
}
然后,在初始化的后段,clocksource 的代碼會把全局變量 curr_clocksource 設(shè)置為上述的 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);
當(dāng)然,如果平臺級的代碼在初始化時也會注冊真正的硬件 clocksource,所以經(jīng)過 clocksource_select 函數(shù)后,curr_clocksource 將會被設(shè)為最合適的 clocksource。如果 clocksource_select 函數(shù)認(rèn)為需要切換更好的時鐘源,它會通過 timekeeping_notify 通知 timekeeping 系統(tǒng),使用新的 clocksource 進(jìn)行時間計數(shù)和更新操作。
原文地址:https://blog.csdn.net/DroidPhone/article/details/7975694
