1. 程式人生 > >Linux時間子系統(十五) clocksource

Linux時間子系統(十五) clocksource

div fin 嵌入式 服務 device 角度 rap Owner 浮點

一、前言

和洋蔥一樣,軟件也是有層次的,內核往往需要對形形色色的某類型的驅動進行抽象,屏蔽掉其具體的特質,獲取該類驅動共同的邏輯,而又根據這些邏輯撰寫該類驅動的抽象層。嵌入式系統總是會提供timer的硬件block,軟件需要對timer硬件提供的功能進行抽象:linux kernel將timer類型的硬件抽象成兩個組件,一是free running的counter,另外一個是指定的counter值上產生中斷的能力。本文主要描述第一個組件,在內核中被稱作clock source。

二、什麽是clocksource?

1、基礎概念

我們先回到一個基礎的問題上來:什麽是時間?時間其實可以抽象成一條直線,沒有起點,也沒有終點(所以我們叫它timeline)。我們可以以一定的刻度來劃分這條直線,例如以1秒的間隔。這樣還不行,還需要一個參考點,例如在耶穌誕辰的時間定位0點。OK,大家終於可以時間的定義上統一了,例如我可以和wowo同學約定在68365287秒的時間一起去吃飯,恩,看起來不是那麽方便,還是可以對以秒計算的時間進行grouping,因此有了年、月、日、時、分這些概念。

對於內核,它的timeline是怎樣的呢?首先時間不是一條直線,因為硬件的計數不可能是無限制的,我們總是使用有限的bit數目的硬件來計數,因此,對於內核(更準確的說對於計算機系統),其時間更像是一個不斷重復的線段,最終也是形成一個沒有起點也沒有終點的timeline。此外,和人不同的是,機器不喜歡grouping,它們更喜歡用一個絕對的數字來標識當前的時間(當然,機器是為人服務的,最終會在人機界面的部分轉換成年、月、日、時、分、秒這樣的格式)。

2、內核數據結構

所謂clock source就是用來抽象一個在指定輸入頻率的clock下工作的一個counter。輸入頻率可以確定以什麽樣的精度來劃分timeline(假設輸入counter的頻率是1GHz,那麽一個cycle就是1ns,也就是說timeline是用1ns來劃分的,最大的精度就是1ns),counter的bit數確定了組成timeline上的“線段”的長度。內核中的struct clocksource 定義如下:

struct clocksource {
cycle_t (*read)(struct clocksource *cs);-----------------(1)
cycle_t cycle_last;
cycle_t mask;
u32 mult;
u32 shift;
u64 max_idle_ns;
u32 maxadj;

const char *name;--------------------------(2)
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);

#ifdef CONFIG_CLOCKSOURCE_WATCHDOG---------------(3)
struct list_head wd_list;
cycle_t cs_last;
cycle_t wd_last;
#endif
struct module *owner;
} ____cacheline_aligned;

struct clocksource的成員分成3組,我們分別介紹:

(1)這部分的代碼是和計時有關的,kernel會頻繁的訪問這些數據結構,因此最好把它們放到一個cacheline中,struct clocksource這個數據結構有cacheline aligned的屬性,任何定義的變量都是對齊到cacheline的,而這些計時相關的成員被放到clocksource數據結構的前面就是為了提高cache hit。一旦訪問了read成員,隨後的幾個成員也就被加載到cacheline中,從而提高的性能。對於clock source抽象的counter而言,其counter value都是針對clock計數的,具體一個clock有多少個納秒是和輸入頻率相關的。

通過read獲取當前的counter value,這個計數值是基於cycle的。不過,對於用戶和其他driver而言,cycle數據是沒有意義的,最好統一使用納秒這樣的單位,因此在struct clocksource中就有了mult和shift這兩個成員了。我們先看看如何將A個cycles數轉換成納秒,具體公式如下:

轉換後的納秒數目 = (A / F) x NSEC_PER_SEC

這樣的轉換公式需要除法,絕大部分的CPU都有乘法器,但是有些處理器是不支持除法,當然,對於不支持硬件除法器的CPU上,toolchain中會提供除法的代碼庫,雖然我們無法將除法操作的代碼編譯成一條除法的匯編指令,但是也可以用代碼庫中的其他運算來取代除法。這樣做的壞處就是性能會受影響(想到這裏的代碼會被內核其他模塊反復調用,你是不是很心疼那些CPU的MIPS?)。腫麽辦?要知道,linux kernel的目標是星辰大海,它是打算服務多種CPU體系結構,並且要有優秀性能表現的。把1/F變成浮點數,這樣就可以去掉除法了,但是又引入了浮點運算,kernel是不建議使用浮點運算的(為何如此建議,你有考慮過嗎?請把答案寄到蝸窩科技大廈,可以贏取內核之旅門票一張,呵呵~~~)。解決方案很簡單,使用移位操作,具體可以參考clocksource_cyc2ns的操作:

static inline s64 clocksource_cyc2ns(cycle_t cycles, u32 mult, u32 shift)
{
return ((u64) cycles * mult) >> shift;
}

也就是說,通過clock source的read函數獲取了cycle數目,乘以mult這個因子然後右移shift個bit就可以得到納秒數。這樣的操作雖然性能比較好,但是損失了精度,還是那句話,設計是平衡的藝術,看你自己的取舍。 其他的成員我們會在後面的代碼分析中給出解釋。

(2)這段的代碼訪問沒有那麽頻繁,主要是和一些系統操作相關。例如list成員。系統將所有已經註冊clocksource掛在一個鏈表中,list就是掛入鏈表的節點。suspend和resume是和電源管理相關,enable和disable是啟停該clock source的callback函數。rating是描述clock source的精度的,毫無疑問,一個輸入頻率是20MHz的counter精度一定是大於10ms一個cycle的counter。關於rating,內核代碼的註釋已經足夠了,如下:

1-99: Unfit for real use
Only available for bootup and testing purposes.
100-199: Base level usability.
Functional for real use, but not desired.
200-299: Good.
A correct and usable clocksource.
300-399: Desired.
A reasonably fast and accurate clocksource.
400-499: Perfect
The ideal clocksource. A must-use where available.

clock source flag描述一些該clock source的特性,我們會在後面的代碼分析中給出更詳細的信息。

(3)第三部分的成員和clocksource watch dog相關。大家比較熟悉是系統的watch dog,主要監視系統,如果不及時餵狗,系統就會reset。clocksource watch dog當然是用來監視clock source的運行情況,如果watch dog發現它監視的clocksource的精度有問題,會修改其rating,告知系統。後面我們會有專門一個章節來描述這部分的內容。

三、註冊和註銷clock source

這個接口主要是提供給底層clock source chip driver使用的,在底層的driver初始化的時候會調用該接口向系統註冊clock source。註銷clock source是註冊的逆過程。

1、註冊函數1:調用者已經計算好mult和shift

如果在驅動代碼中已經根據輸入頻率設定好了mult和shift,那麽內核的clocksource layer是不需要進行這部分的內容的計算,那麽註冊clock source就會比較簡單,代碼如下:

int clocksource_register(struct clocksource *cs)
{
cs->maxadj = clocksource_max_adjustment(cs);---------------(1)

cs->max_idle_ns = clocksource_max_deferment(cs); -------------(2)

mutex_lock(&clocksource_mutex);----------------------(3)
clocksource_enqueue(cs);--------------------------(4)
clocksource_enqueue_watchdog(cs);---------------------(5)
clocksource_select();----------------------------(6)
mutex_unlock(&clocksource_mutex);
return 0;
}

(1)在給定的mult和shift組合下,計算最大的mult調整值。時間是需要校準的,首先counter的輸入頻率是有誤差的,此外,在計算納秒的時候mult和shift又引入了誤差,這些誤差可以通過外部的一些基準值進行校準(例如NTP server)。不過由於在將cycle值轉換成納秒值的過程中(((u64) cycles * mult) >> shift),太大的mult值會導致溢出,因此對multi的修正不應該過大,需要有一個限度,maxadj就是這個限度。clocksource_max_adjustment的代碼很簡單,就是返回clock source中mult成員11%的值(為何是11%呢?)。

此外,需要註意的是:這裏有潛在溢出的風險,用戶設定的mult是u32的,有可能已經逼近最大值,mult + maxadj有可能導致32bit的溢出(當然,這是在最大調整值的情況下)。

(2)我們前面已經了解了將counter value(cycle值)轉換成ns的公式,這個公式是有限制的,cycle值不能太大,如果太大的話,cycles * mult的結果也會超出64bit從而導致溢出。因此,max_idle_ns 就是說明保證從cycle到ms不產生溢出的那個最大的納秒數。為何其名字中有個idle呢?這是因為最大的cycle值是出現在idle的時候。我們知道,傳統的Unix都是有一個周期性的tick,在嵌入式系統中,我們經常設定為10ms,但是linux kernel也允許你配置成NO_HZ,這時候,系統就不存在那個周期性的tick了,如果系統沒有任何的動作是否CPU就一直idle下去了呢?不會的,由於counter value和納秒的轉換限制,這個idle的時間不能超過max_idle_ns。具體的計算很簡單,這裏不再具體描述。計算max_idle_ns的時候,為了安全還設定了12.5%的margin,之所以選擇12.5%,是因為可以通過移位(而不是除法)的操作完成,看,kernel工程師永遠都是performance至上的。

(3)通過clocksource_mutex保護對clock sourct list這一全局共享資源的訪問。

(4)將該clock source按照rating的順序插入到全局clock source list中

(5)處理clock source watchdog相關的內容,後面會詳細介紹

(6)選擇一個合適的clock source。kernel當然是選用一個rating最高的clocksource作為當前的正在使用的那個clock source。當註冊一個新的clock source的時候當然要調用clocksource_select,畢竟有可能註冊了一個精度更高的clock
source。

2、註冊函數2:調用者給出輸入clock頻率,需要在註冊過程中計算mult、shift和max_idle_ns

clock source chip driver可以調用clocksource_register_hz或者clocksource_register_khz這兩個接口函數來註冊一個clocksource。具體的接口定義如下:

static inline int clocksource_register_hz(struct clocksource *cs, u32 hz)

static inline int clocksource_register_khz(struct clocksource *cs, u32 khz)

hz和khz參數是傳遞counter的輸入頻率信息。這兩個接口函數實際是調用了__clocksource_register_scale函數進行具體的操作,代碼如下:

int __clocksource_register_scale(struct clocksource *cs, u32 scale, u32 freq)
{
__clocksource_updatefreq_scale(cs, scale, freq);

將該clocksource掛入全局鏈表,代碼同上。
return 0;
}

最核心的代碼就是__clocksource_updatefreq_scale這個函數,代碼如下:

void __clocksource_updatefreq_scale(struct clocksource *cs, u32 scale, u32 freq)
{
u64 sec;

sec = (cs->mask - (cs->mask >> 3));-------------------(1)
do_div(sec, freq);
do_div(sec, scale);
if (!sec)
sec = 1;--------限制最小值
else if (sec > 600 && cs->mask > UINT_MAX)
sec = 600; ------------------------------(2)

clocks_calc_mult_shift(&cs->mult, &cs->shift, freq,--------------(3)
NSEC_PER_SEC / scale, sec * scale);

cs->maxadj = clocksource_max_adjustment(cs);---------------(4)
while ((cs->mult + cs->maxadj < cs->mult)
|| (cs->mult - cs->maxadj > cs->mult)) {
cs->mult >>= 1;
cs->shift--;
cs->maxadj = clocksource_max_adjustment(cs);
}

cs->max_idle_ns = clocksource_max_deferment(cs);
}

(1)硬件counter本身就是由有限個bit組成,因此它能表示的時間有一個最大的限制,超過之後counter就溢出了。根據counter的bit數目以及輸入頻率可以計算出該硬件counter能表示的最大的時間長度。cs->mask最是最大的cycle數目,除以頻率就是能表示的最大的時間範圍(以秒為單位)。因此,代碼至此,我們知道,[0, sec秒]就是該counter能表示的以秒計算的時間範圍。這裏還引入了12.5%的margin,具體原因在上面clocksource_max_deferment函數中已經說明。

(2)在解析這裏的代碼之前,我們先考慮這樣一個問題:如何獲取最佳的mult和shift組合?mult這個因子一定是越大越好了,越大精度越高,但是,我們的運算過程都是64 比特的,也就是說,中間的結果(cycle x mult)不能超過64bit。對於32bit的count,這個要求還好,因為cycle就是32bit的,只要mult不超過32bit就OK了(mult本來就是u32的)。因此,對於那些超過32 bit的count,我們就需要註意了,我們要人為的限制最大時間值,也就是說,雖然硬件counter可以表示出更大的時間值,但是通過mult和shift將這個大的cycle值轉成ns值的時候,如果cycle太大,勢必導致mult要小一些(否則溢出),而mult較小會導致精度不足,而精度不足(這時候如果要保證轉換算法正確,mult需要取一個較小的數值,從而降低了精度)導致轉換失去意義,因此,這裏給出一個600秒的限制,這個限制也是和max idle ns相關的,具體參考上面的描述。

其實這裏仍然是一個設計平衡的問題:一方面希望保持精度,因此mult要大,cycle到ns轉換的時間範圍就會小。另外一方面,cpuidle模塊從電源管理的角度看,當然希望其在沒有任務的時候,能夠idle的時間越長越好,600秒是一個折衷的選擇,讓雙方都基本滿意。

(3)進入真正的mult和shift計算時刻了,代碼如下:

void clocks_calc_mult_shift(u32 *mult, u32 *shift, u32 from, u32 to, u32 maxsec)
{
u64 tmp;
u32 sft, sftacc= 32;

tmp = ((u64)maxsec * from) >> 32;-------------------(a)
while (tmp) {
tmp >>=1;
sftacc--;------------------------------(b)
}

for (sft = 32; sft > 0; sft--) {-----------------------(c)
tmp = (u64) to << sft;
tmp += from / 2;
do_div(tmp, from);--------------------------(d)
if ((tmp >> sftacc) == 0)
break;
}
*mult = tmp;
*shift = sft;
}

(a)在這個場景中,from是count的輸入頻率,maxsec是最大的表示的範圍。maxsec * from就是將秒數轉換成了cycle數目。對於大於32 bit的counter而言,最大的cycle數目有可能需要超過32個bit來表示,因此這裏要進行右移32bit的操作。對於32 bit以下的counter,這時候的tmp右移32bit之後一定會等於0,而對於大於32 bit的counter,tmp是一個非0值。

(b)sftacc保存了左移多少位才會造成最大cycle數(對應最大的時間範圍值)的溢出,對於32 bit以下的counter,統一設定為32個bit,而對於大於32 bit的counter,sftacc需要根據tmp值(這時候tmp保存了最大cycle數的高32 bit值)進
行計算。

(c)如何獲取最佳的mult和shift組合?我們之前已經回答這個問題了,現在進入實現的層面。當一個公式中有兩個可變量的時候,最好的辦法就是固定其中一個,求出另外一個,然後帶入約束條件進行檢驗。我們首先固定shift這個參數。mult這個因子一定是越大越好,mult越大也就是意味著shift越大。當然shift總有一個起始值,我們設定為32bit,因此sft從32開始搜索,看看是否滿足最大時間範圍的要求。如果滿足,那麽就找到最佳的mult和shift組合,否則要sft遞減,進行下一輪搜索。

(d)我們先考慮如何計算mult值。根據公式cycles * mult) >> shift可以得到ns數,由此可以得到計算mult值的公式:

mult = (ns<<shift)/cycles

如果我們設定ns數是10^9納秒(也就是1秒)的話,cycles數目就是頻率值(所謂頻率不就是1秒振蕩的次數嘛)。因此上面的公式可以修改為:

mult = (NSEC_PER_SEC<<shift)/HW counter input frequency


看看上面的公式,再對照代碼,一切就很清晰了,這裏的tmp就是得到了一個mult值。這個mult值是否合適?不一定,因此這個mult值有可能導致溢出(轉換的時候,我們要保證最大時間範圍對應的cycle數目乘以這個mult值不能造成64 bit的溢出),如果溢出的話,只能說明mult值還是要小一點哦,從而進入下一次的loop。如何判斷mult值會溢出?我們可以看下面的圖片:

技術分享圖片

在步驟b中計算得到的sftacc就是multi的最大的bit數目。因此,(tmp >> sftacc)== 0就是判斷找到最優mult的條件。

(4)這段代碼主要是計算clock source的maxadj成員。由於mult有可能被NTP修改,NTP會根據情況會增加或者減少mult的值。我們設定NTP的修正在11%左右,因此clocksource_max_adjustment很是非常直觀的,這裏就不再描述了。

(5)檢查maxadj設定是否OK,是否會溢出,如果是的話,說明mult值還是太大,那麽需要還是要降低。

(6)計算max_idle_ns。

3、註銷clocksource

clocksource_unregister函數用來註銷一個clocksource,其主要的邏輯都在clocksource_unbind中,代碼如下:

static int clocksource_unbind(struct clocksource *cs)
{
if (clocksource_is_watchdog(cs))
return -EBUSY; ----不能註銷watchdog機制中的基準clocksource

if (cs == curr_clocksource) {---註銷當前clock source的處理
clocksource_select_fallback();----選擇一個新的當前clock source
if (curr_clocksource == cs)
return -EBUSY;-----是系統中的唯一的clock source,不能註銷
}
clocksource_dequeue_watchdog(cs);-----將該節點從watchdog的全局列表中取下來
list_del_init(&cs->list);--------將該節點從clocksource的全局列表中取下來
return 0;
}

四、如何挑選clock source

1、系統在什麽時候會啟動選擇clock source的過程?

主要context如下:

(1)註冊一個新的clock source。有新貨到來,總是要再挑挑揀揀吧,說不定會有新發現。

(2)註銷clock source。如果註銷掉的就是current clock source,總得在剩下的矮子中選一個將軍吧

(3)在clock source watchdog中啟動。具體參考下一章描述

(4)底層的clock source chip driver調用clocksource_change_rating修改rating。底層的clock source chip driver有可能自廢武功,也有可能滿血復活,這時候當然要重新選舉,否則有可能廢材當盟主。

(5)來自用戶空間的請求。用戶空間的程序可以通過current_clocksource的屬性文件強行指定current clocksource。這時候,用戶空間程序會給出clock source的名字,內核將用戶空間向設定的名字寫入override_name buffer,然後調用clocksource_select函數。

2、選舉最優clock source的過程

調用clocksource_select函數可以啟動選舉最優clock source的過程,而該函數實際上是調用__clocksource_select來完成具體的操作,__clocksource_select代碼如下:

static void __clocksource_select(bool skipcur)-----傳入參數表示是否skip current clock source
{
bool oneshot = tick_oneshot_mode_active();
struct clocksource *best, *cs;

best = clocksource_find_best(oneshot, skipcur);-------------(1)

list_for_each_entry(cs, &clocksource_list, list){--------------(2)
if (skipcur && cs == curr_clocksource)
continue;
if (strcmp(cs->name, override_name) != 0)----不是用戶指定的那個,忽略之
continue;

if (!(cs->flags & CLOCK_SOURCE_VALID_FOR_HRES) && oneshot) {
override_name[0] = 0;-------上帝錯了,修正之
} else
best = cs;-------OK,用戶是上帝,把用戶指定的設定為best
break;
}

if (curr_clocksource != best && !timekeeping_notify(best)){----------(3)
curr_clocksource = best;
}
}

(1)找到最好的那個clock source。oneshot這個參數表示本CPU的tick device的工作模式,這個工作模式有兩種,一種是周期性tick,也就是大家熟悉的傳統的tick。另外一種叫做one shot模式,更詳細的信息請參考Linux時間子系統之
(十三):Dynamic tick。由於工作在one shot模式下的tick device對clock source有特別的需求,因此ocksource_find_best函數需要知道本CPU的tick device的工作模式。具體代碼如下:

static struct clocksource *clocksource_find_best(bool oneshot, bool skipcur)
{
struct clocksource *cs;

if (!finished_booting || list_empty(&clocksource_list))--------------(a)
return NULL;

list_for_each_entry(cs, &clocksource_list, list) {----------------(b)
if (skipcur && cs == curr_clocksource)
continue;
if (oneshot && !(cs->flags & CLOCK_SOURCE_VALID_FOR_HRES))
continue;
return cs;
}
return NULL;
}

(a)系統中的timer硬件有可能包括多個,這些HW timer的驅動都會向系統註冊clock source,這導致在初始化的過程中,current clock source會變來變去。與其這樣,不如等到塵埃落定(系統啟動完畢,各個clock source chip完成初始
化)的時候再啟動clock source的甄選。finished_booting就是啟這個作用的。當然,clock source全局列表是空的話也會返回NULL。

(b)實際的選擇當然是rating最高的那個clock source(也就是clock sourcelist中最前面的節點)。但是,如果當前是one shot模式,那麽clock source是需要設定CLOCK_SOURCE_VALID_FOR_HRES這個flag的。

(2)這段代碼是處理用戶空間指定current clock source的請求。用戶空間程序會將其心儀的clock source的名字放入到override_name中,在clocksourceselect的時候需要scan clock source列表,找到用戶指定的那個clock source,
並將其設定為best。註意:這裏會覆蓋上面clocksource_find_best函數中找到的那個best clock source。當然,用戶雖然是上帝,但是用戶也許是愚蠢的,他有可能指定錯誤的clock source。例如其指定的clock source不支持高精度timer和NO HZ的配置,但是當前的系統恰恰是需要這種能力的。這時候,我們當然不能縱然用戶。

(3)調用timekeeping_notify函數通知timekeeping模塊。

五、clock source watchdog

呵呵~~~有空再寫吧。

六、用戶空間接口

1、sysfs接口初始化

在系統初始化的時候會調用init_clocksource_sysfs函數來初始化clock source的sys file system接口,如下:

static int __init init_clocksource_sysfs(void)
{
int error = subsys_system_register(&clocksource_subsys, NULL); --------(1)

if (!error)
error = device_register(&device_clocksource);----------------(2)
if (!error)
error = device_create_file(-------------------------(3)
&device_clocksource,
&dev_attr_current_clocksource);
if (!error)
error = device_create_file(&device_clocksource,
&dev_attr_unbind_clocksource);
if (!error)
error = device_create_file(
&device_clocksource,
&dev_attr_available_clocksource);
return error;
}

device_initcall(init_clocksource_sysfs);

(1)一路陪著linux kernel走來的工程師應該對sysdev_class、sys_device、sysdev_driver以及SYSDEV_ATTR這些定義有印象。linux kernel提供了一個pseudo-bus,這條bus主要是用於cpus,中斷控制器,timer等系統設備。sysdev_class、sys_device和sysdev_driver組成系統設備三件套,對應設備模型只能夠的bus type,device和driver。其實system device模型中需要處理的也是和linux設備模型中類似的邏輯:註冊設備、註冊driver、driver和設備的匹配等
等。當然也有不同的地方,例如:在電源管理過程中,系統進入suspend狀態的時候,優先處理其他的設備,最後處理system設備,喚醒的時候,先喚醒systemdevice,然後是其他普通設備。系統設備是和其他普通設備息息相關的,因此需要首先喚醒,只有這樣處理,普通設備在喚醒後才能正常使用系統設備提供的功能。Anyway,雖有不同,但是從high level的層面看還是大部分相同的,用兩套邏輯來處理類似的東西還是看起來有些怪異的。因此,3.3的kernel代碼廢除了systemdevice機制,使用統一設備模型來處理系統設備。

能統一處理當然好,又回到大家熟悉的bus type,device和driver的路子上來。但是,kernel不是活在真空中,大量的AP軟件使用了舊的system device機制提供的sysfs接口,為了兼容,subsys_system_register這樣的函數被設計出來,調用該接口便創建和舊的system device機制一樣的sysfs接口。

(2)OK,回歸設備模型當然要定義三件套了,bus type定義如下:

static struct bus_type clocksource_subsys = {
.name = "clocksource",
.dev_name = "clocksource",
};

device定義如下:

static struct device device_clocksource = {
.id = 0,
.bus = &clocksource_subsys,
};

調用device_register就可以把clock source這個device加入到系統中,統一設備模型會幫我們做一切事情(例如設定clock source這個device對象的名字。作為一個object,clock source device需要一個名字,就是其kobject成員的名字。內核允許將設備的名字留空,當調用device_register函數將設備註冊到設備模型中後,設備模型會將"bus->dev_name+device ID”的名字賦給該設備對象,看,多麽的貼心)。

(3)原來使用SYSDEV_ATTR定義的系統設備屬性先改成使用普通的DEVICE_ATTR,具體如下:

static DEVICE_ATTR(current_clocksource, 0644,
sysfs_show_current_clocksources,
sysfs_override_clocksource);

static DEVICE_ATTR(unbind_clocksource, 0200, NULL,
sysfs_unbind_clocksource);

static DEVICE_ATTR(available_clocksource, 0444,
sysfs_show_available_clocksources, NULL);

系統中有多個clocksource,通過available_clocksource這個設備屬性可以知道當前系統有多少個可用的clocksource。雖然系統有多個clocksource,但是內核會以一定的策略選擇一個(例如rating最高的哪個)作為當前的clocksource,這個可以通過current_clocksource這個屬性文件獲得當前系統正在使用的clocksource。對該屬性文件寫可以設定current clock source。unbind_clocksource屬性是write only的,該接口可以對某個clocksource進行unbind的操作。

調用device_create_file函數為clocksource設備創建上面定義的三個屬性。

七、提供給其他driver計時用的接口函數

1、為何會有timecounter和cyclecounter

在內核的driver中,我們可能有這樣的需求:獲取drive中的A事件和B事件之間的時間值或者一個event stream過程中,各個event的時刻值。這裏,driver不關心絕對的時間點,關心的是事件之間的時長。為了應對這個需求,clock source模塊提供了timecounter和cyclecounter。

內核中使用struct cyclecounter 來抽象一個free running的counter,從0開始,不斷累加。由於counter的bit數目有限,因此,某個時間後,counter會wraparound,從0繼續開始。該數據結構定義如下:

struct cyclecounter {
cycle_t (*read)(const struct cyclecounter *cc);--獲取當前的counter value,單位是cycle
cycle_t mask;------------該count有多少個bit?
u32 mult;--------------轉換成ns需要的乘積因子
u32 shift; --------------轉換成ns需要的右移因子
};

每個cycle counter的counter value都是針對clock計數的,因此,通過read獲取的counter value是基於cycle的,而cycle又是和輸入頻率有關。不過,對於其他driver而言,cycle數據是沒有意義的,最好統一使用納秒這樣的單位,因此在
struct cyclecounter 中就有了mult和shift這兩個成員了。讀到這裏,我相信大部分的讀者都會怒吼:你這些文字在描述clock source的時候就說過一遍,現在又拿出來騙。呵呵~~稍安勿躁,實際上內核的確把一個HW block抽象成了兩個數據結構,具體參考下圖:

技術分享圖片

實際上,最開始的時候,內核的確是只有clock source模塊,它位於timekeeping模塊和硬件之間。但是,其他內核模塊也有訪問free running counter的需要,這時候,內核開發人員創建了cycle counter和timer counter這樣的概念,雖然代碼有一點重復,但是這樣不會觸及clock source代碼的改動。

timecounter是構架在cycle counter之上,使用納秒這樣的時間單位而不是cycle數目,這樣的設計會讓用戶接口變得更加友好,畢竟大家還是喜歡直觀的納秒值。timecounter的定義如下:

struct timecounter {
const struct cyclecounter *cc;-----------該timer
counter base的cycle counter
cycle_t cycle_last;----------------上一次訪問的
counter value
u64 nsec;--------------------當前的納秒值
};

2、如何使用timecounter的接口

首先需要初始化,代碼如下:

void timecounter_init(struct timecounter *tc, const struct
cyclecounter *cc, u64 start_tstamp)
{
tc->cc = cc;-------------該time counter需要哪一個
cycle counter?
tc->cycle_last = cc->read(cc);-----獲取初始化時刻HW counter
的counter value
tc->nsec = start_tstamp;-------設定納秒的基準值
}

如果start_tstamp等於0的話,在調用timecounter_init這個時刻被定義為0納秒。之後,驅動代碼可以調用timecounter_read函數來獲取當前的時間值(基於start_tstamp的),代碼如下:

u64 timecounter_read(struct timecounter *tc)
{
u64 nsec;
nsec = timecounter_read_delta(tc);--離上次調用timecounter_init或者timecounter_read過去了

----多少時間了?
nsec += tc->nsec;
tc->nsec = nsec; -----------設定當前時間

return nsec;
}

Linux時間子系統(十五) clocksource