linux核心設計與實現 —— 定時器和時間管理(第11章)
核心中的時間概念
硬體為核心提供了一個系統定時器用以計算流逝的時間。系統定時器是一種可程式設計硬體晶片,它能以固定頻率產生中斷。該頻率可以通過程式設計預定,稱作節拍率(tick rate)。該中斷就是所謂的定時器中斷,它所對應的中斷處理程式負責更新系統時間,也負責執行需要週期性執行的任務。
節拍率Hz
系統定時器頻率(節拍率)是通過靜態預處理定義的,也就是HZ(赫茲),在系統啟動時按照HZ值對硬體進行設定。體系結構不同,HZ的值也不同。
核心在asm/param.h檔案中定義了這個值。節拍率有一個HZ頻率,一個週期為1/HZ秒。x86體系結構中,系統定時器頻率預設為100。
jiffies
全域性變數jiffies用來記錄自系統啟動以來產生的節拍的總數。
jiffies定義於檔案 linux/jiffies.h 中:
extern unsigned long volatile jiffies;
將以秒為單位的時間轉化為jiffies: (seconds * HZ)
將jiffies轉換為以秒為單位的時間:(jiffies/HZ)
使用者空間和HZ
在2.6版以前的核心中,如果改變核心中HZ的值,會給使用者空間中某些程式造成異常結果。為了避免這個錯誤,核心定義了USER_HZ來代表使用者空間看到的HZ值。
核心使用函式jiffies_to_clock_t()或jiffies_64_to_clock_t()將jiffies值的單位從HZ轉換為USER_HZ。
定時器
核心定時器是管理核心流逝的時間的基礎,與下半部將工作推後執行不同,核心定時器能夠使工作在指定時間點上執行。
核心定時器也稱為動態定時器,因為這種定時器超時之後就自動撤銷,如果需要週期執行,就必須得不斷地建立和撤銷。
1. 使用定時器
定時器由結構體timer_list表示,定義在檔案 linux/timer.h 中。
struct timer_list {
struct list_head entry; /* 定時器連結串列的入口 */
unsigned long expires; /* 以jiffies為單位的定時值 */
void (*function)(unsigned long); /* 定時器處理函式 */
unsigned long data; /* 傳給處理函式的長整型引數 */
struct tvec_t_base_s *base; /* 定時器內部值,使用者不要使用 */
};
建立定時器時需要定義它:
struct timer_list my_timer;
然後初始化:
init_timer(&my_timer);
然後填充結構中需要的值:
my_timer.expires = jiffies + delay; /* 定時器超時時的節拍數 */
my_timer.data = 0; /* 給定時器處理函式傳入0值 */
my_timer.function = my_function; /* 定時器超時時呼叫的函式 */
處理函式必須符合下面的函式原型:
void my_timer_function(unsigned long data);
最後啟用定時器:
add_timer(&my_timer);
其他定時器相關函式:
/**
* 修改定時器
*/
int mod_timer(struct timer_list *timer, unsigned long expires);
/**
* 刪除定時器
*/
int del_timer(struct timer_list *timer);
/**
* 刪除定時器,刪除前,等待多處理器上的定時器處理程式都退出
*/
int del_timer_sync(struct timer_list *timer);
2. 定時器處理函式
核心在時鐘中斷髮生後執行定時器,定時器作為軟中斷在下半部的上下文中執行。具體來說,時鐘中斷處理程式會執行update_process_times函式,該函式隨機呼叫run_local_timers函式:
void run_local_timers(void)
{
hrtimer_run_queues();
raise_softirq(TIMER_SOFTIRQ); /* 執行定時器軟中斷 */
softlockup_tick();
}
延遲執行
1. 忙等待
最簡單的延遲方法是忙等待(或者說忙迴圈),例如:
unsigned long delay = jiffies + 2*HZ; /* 延遲2秒 */
while (time_before(jiffies, delay));
這是一種非常非常低效率的方法,稍微好一點的,是在程式碼等待時,允許核心重新排程執行其他任務:
unsigned long delay = jiffies + 2*HZ; /* 延遲2秒 */
while (time_before(jiffies, delay))
cond_resched();
2. 短延遲
核心提供了三個可以處理ms、ns和ms級別的延遲函式,它們定義在檔案 linux/delay.h 和 asm/delay.h 中,
void udelay(unsigned long usecs);
void ndelay(unsigned long nsecs);
void mdelay(unsigned long msecs);
udelay()函式依靠執行數次迴圈達到延遲效果,而mdelay()函式又是通過udelay()函式實現的。
3. schedule_timeout()
更理想的延遲執行方法是使用schedule_timeout()函式,該函式會讓需要延遲執行的任務睡眠到指定的延遲時間耗盡後再重新執行。schedule_timeout()函式是核心定時器的一個簡單應用。