linux核心分析筆記----定時器和時間管理
在這一次裡,主要講講和時間相關的東西,這個我們都比較熟悉,我就直接如主題。
首先要明白兩個概念:系統定時器和動態定時器。週期性產生的事件都是有系統定時器驅動的,這裡的系統定時器是一種可程式設計硬體晶片,它能以固定頻率產生中斷。該中斷就是定時器中斷,它所對應的中斷處理程式負責更新系統時間,也負責執行需要週期行執行的任務。系統定時器和時鐘中斷處理程式是Linux系統核心管理機制中的中樞。動態定時器是用來推遲執行程式的工具。核心可以動態建立或銷燬動態定時器。
核心必須在硬體的幫助下才能計算和管理時間。硬體為核心提供了一個系統定時器用以計算流逝的時間,該時鐘在核心中可看成是一個電子時間資源。系統定時器以某種頻率自行觸發時鐘中斷,該頻率可以通過程式設計預定稱為節拍率(tick rate).當時鍾中斷髮生時,核心就通過一種特殊的中斷處理程式對其進行處理。系統定時器頻率(節拍率)是通過靜態預處理定義的,也就是HZ.在系統啟動時按照HZ值對硬體進行設定。體系結構不一樣,HZ的值也不同,定義在asm/param.h中。剛提到的節拍率就是這個意思。週期是1/HZ秒。最後要說明的是這個HZ值在編寫核心程式碼時,不是固定不變的,而是可調的。當然,對於作業系統而言,也並不是一定要這個固定的時鐘中斷。實際上,核心可以使用動態程式設計定時器操作掛起事件。這裡就不多說了。
在linux核心裡,有一個叫jiffies的變數(定義在linux/jiffies)記錄了自系統啟動以來產生的節拍的總數。啟動時,核心將該變數初始化為0,此後每次時鐘中斷處理程式都會增加該變數的值。因為一秒內時鐘中斷的次數等於HZ,所以jiffies一秒內增加的值也就為HZ.系統執行時間以秒為單位計算,就等於jiffes/HZ.它作為在計算機表示的變數,就總存在大小,當這個變數增加到超出它的表示上限時,就要回繞到0.這個迴繞看起來很簡單,但實際上還是給我們程式設計造成了很大的麻煩,比如邊界條件判斷時。幸好,核心提供了四個巨集來幫助比較節拍計數,這些巨集定義在linux/jiffies.h可以很好的處理節拍迴繞的情況:
說明:unknown引數通常是jiffies,known引數是需要對比的值。
如果改變核心中的HZ的值則會給使用者空間中某些程式造成異常結果,這是因為核心是以節拍數/秒的形式給使用者空間匯出這個值的,在這個介面穩定了很長一段時間後,應用程式便逐漸依賴於這個特定的HZ的值了。所以如果在核心中更改了HZ的定義值,就打破了使用者空間的常量關係----使用者空間並不知道這個新的HZ的值。為了解決這個問題,核心必須更改所有匯出的jiffies的值。核心定義了USER_HZ來代表使用者空間看到的HZ值。核心可以使用巨集jiffies_to_clock_t()將一個由HZ表示的節拍計數轉換成一個由USER_HZ表示的節拍數。改巨集的用法取決於USER_HZ是否為HZ的整數倍或相反。當是整數倍時,巨集的形式相當簡單:
1 |
#define
jiffies_to_clock_t(x) ((x)/(HZ/USER_HZ));
|
如果不是整數倍關係,那麼該巨集就得用更為複雜的演算法了。同樣的,如果是64位系統,核心使用函式jiffies_64_to_clock()將64位的jiffies值的單位從HZ轉換為USER_HZ.
體系結構提供了兩種裝置進行計時:系統定時器和實時時鐘。系統定時器提供一種週期性觸發中斷機制。實時時鐘(RTC)是用來持久儲存系統時間的裝置,即便系統關閉後,它也可以靠主機板上的微型電池提供的電力保護系統的計時。當系統啟動時,核心通過讀取RTC來初始化牆上時間,該時間存放在xtime變數中,實時時鐘最主要的作用是在啟動時初始化xtime變數。
有了上面的概念基礎,下面就分析時鐘中斷處理程式。它分為兩個部分:體系結構相關部分和體系結構無關部分。相關的部分作為系統定時器的中斷處理程式而註冊到核心中,以便在產生時鐘中斷時,它能夠相應地執行。執行的工作如下:
1.獲得xtime_lock鎖,以便對訪問jiffies_64和牆上時間xtime進行保護。 2.需要時應答或重新設定系統時鐘。 3.週期性地使用牆上時間更新實時時鐘。 4.呼叫體系結構無關的時間例程:do_timer(). 中斷服務程式主要通過呼叫與體系結構無關的例程do_timer()執行下面的工作: 1.給jiffies_64變數加1. 2.更新資源消耗的統計值,比如當前程序所消耗的系統時間和使用者時間。 3.執行已經到期的動態定時器. 4.執行scheduler_tick()函式. 5.更新牆上時間,該時間存放在xtime變數中. 6.計算平均負載值. |
do_timer看起來還是很簡單的,應為它的主要工作就是完成上面的框架,具體的讓其它函式做就好了:
?1 2 3 4 5 6 |
void do_timer( struct pt_regs
*regs)
{
jiffies_64++;
update_process_times(user_mode(regs));
update_times();
}
|
上述user_mode()巨集查詢處理器暫存器regs的狀態,如果時鐘中斷髮生在使用者空間,它返回1;如果發生在核心模式,則返回0.update_process_times()函式根據時鐘中斷產生的位置,對使用者或對系統進行相應的時間更新:
?1 2 3 4 5 6 7 8 9 |
void update_process_times( int user_tick)
{
struct task_struct
*p=current;
int cpu=smp_processor_id();
int system =user_tick^1;
updata_one_process(p,user_tick, system ,cpu);
run_local_timers();
scheduler_tick(user_tick, system );
}
|
update_one_process()函式的作用是更新程序時間。它的實現是相當細緻的。但注意,因為使用了XOR操作,所以user_tick和system兩個變數只要其中有一個為1,則另外一個就必須為0,updates_one_process()函式可以通過判斷分支,將user_tick和system加到程序相應的計數上:
?1 2 |
p->utime
= user;
|