1. 程式人生 > >linux核心定時器使用及原理

linux核心定時器使用及原理

轉自:https://wenku.baidu.com/view/cab7028fcc22bcd126ff0c58.html

Linux定時器的使用

核心定時器是核心用來控制在未來某個時間點(基於jiffies)排程執行某個函式的一種機制,其實現位於 <linux/timer.h> 和 kernel/timer.c 檔案中。

被排程的函式肯定是非同步執行的,它類似於一種“軟體中斷”,而且是處於非程序的上下文中,所以排程函式必須遵守以下規則:

1) 沒有 current 指標、不允許訪問使用者空間。因為沒有程序上下文,相關程式碼和被中斷的程序沒有任何聯絡。

2) 不能執行休眠(或可能引起休眠的函式)和排程。

3) 任何被訪問的資料結構都應該針對併發訪問進行保護,以防止競爭條件。

核心定時器的排程函式執行過一次後就不會再被運行了(相當於自動登出),但可以通過在被排程的函式中重新排程自己來週期執行。

在SMP系統中,排程函式總是在註冊它的同一CPU上執行,以儘可能獲得快取的局域性。

       核心定時器的資料結構

struct timer_list {

   struct list_head entry;

   unsigned long expires;

   void (*function)(unsigned long);

   unsigned long data;

   struct tvec_base *base;

   /* ... */

};

其中 expires 欄位表示期望定時器執行的 jiffies 值,到達該 jiffies 值時,將呼叫 function 函式,並傳遞 data 作為引數。當一個定時器被註冊到核心之後,entry 欄位用來連線該定時器到一個核心連結串列中。base 欄位是核心內部實現所用的。

需要注意的是 expires的值是32位的,因為核心定時器並不適用於長的未來時間點。

初始化

在使用 structtimer_list 之前,需要初始化該資料結構,確保所有的欄位都被正確地設定。初始化有兩種方法。

方法一:

DEFINE_TIMER(timer_name, function_name,expires_value, data);

該巨集會定義一個名叫timer_name 核心定時器,並初始化其function, expires, name 和 base 欄位。

方法二:

struct timer_list mytimer;

setup_timer(&mytimer, (*function)(unsignedlong), unsigned long data);

mytimer.expires = jiffies + 5*HZ;

注意,無論用哪種方法初始化,其本質都只是給欄位賦值,所以只要在執行 add_timer() 之前,expires, function 和 data 欄位都可以直接再修改。

關於上面這些巨集和函式的定義,參見 include/linux/timer.h。

註冊

定時器要生效,還必須被連線到核心專門的連結串列中,這可以通過 add_timer(struct timer_list *timer) 來實現。

重新註冊

要修改一個定時器的排程時間,可以通過呼叫 mod_timer(struct timer_list *timer, unsigned long expires)。mod_timer() 會重新註冊定時器到核心,而不管定時器函式是否被執行過。

登出

登出一個定時器,可以通過del_timer(struct timer_list *timer) 或 del_timer_sync(struct timer_list *timer)。其中 del_timer_sync 是用在 SMP 系統上的(在非SMP系統上,它等於del_timer),當要被登出的定時器函式正在另一個 cpu 上執行時,del_timer_sync() 會等待其執行完,所以這個函式會休眠。另外還應避免它和被排程的函式爭用同一個鎖。對於一個已經被執行過且沒有重新註冊自己的定時器而言,登出函式其實也沒什麼事可做。

int timer_pending(const struct timer_list*timer)

這個函式用來判斷一個定時器是否被新增到了核心連結串列中以等待被排程執行。注意,當一個定時器函式即將要被執行前,核心會把相應的定時器從核心連結串列中刪除(相當於登出)

一個簡單的例子

#include <linux/module.h>

#include <linux/timer.h>

#include <linux/jiffies.h>

struct timer_list mytimer;

static void myfunc(unsigned long data)

{

       printk("%s\n", (char *)data);

       mod_timer(&mytimer,jiffies + 2*HZ);

}

static int __init mytimer_init(void)

{

       setup_timer(&mytimer, myfunc, (unsigned long)"Hello,world!");

       mytimer.expires =jiffies + HZ;

        add_timer(&mytimer);

        return 0;

}

static void __exit mytimer_exit(void)

{

        del_timer(&mytimer);

}

module_init(mytimer_init);

module_exit(mytimer_exit);

*******************************************************************************

----------------------------------------------------------------------------------------------------------------------
7.6.1 Linux核心對定時器的描述 
Linux核心2.4版中去掉了老版本核心中的靜態定時器機制,而只留下動態定時器。相應地在timer_bh()函式中也不再通過run_old_timers()函式來執行老式的靜態定時器。動態定時器與靜態定時器這二個概念是相對於Linux核心定時器機制的可擴充套件功能而言的,動態定時器是指核心的定時器佇列是可以動態變化的,然而就定時器本身而言,二者並無本質的區別。考慮到靜態定時器機制的能力有限,因此Linux核心2.4版中完全去掉了以前的靜態定時器機制。 

timer_create(2):建立了一個定時器。

timer_settime(2):啟動或者停止一個定時器。

timer_gettime(2):返回到下一次到期的剩餘時間值和定時器定義的時間間隔。出現該介面的原因是,如果使用者定義了一個 1ms 的定時器,可能當時系統負荷很重,導致該定時器實際山 10ms 後才超時,這種情況下,overrun=9ms

timer_getoverrun(2):返回上次定時器到期時超限值。

timer_delete(2):停止並刪除一個定時器。

上面最重要的介面是 timer_create(2),其中,clockid 表明了要使用的時鐘型別,在 POSIX 中要求必須實現 CLOCK_REALTIME 型別的時鐘。 evp 引數指明瞭在定時到期後,呼叫者被通知的方式。該結構體定義如下 :


Linux在include/linux/timer.h標頭檔案中定義了資料結構timer_list來描述一個核心定時器: 


struct timer_list { 
    struct list_head list; 
    unsigned long expires; 
    unsigned long data; 
    void (*function)(unsigned long); 
};
 

各資料成員的含義如下: 

(1)雙向連結串列元素list:用來將多個定時器連線成一條雙向迴圈佇列。 

(2)expires:指定定時器到期的時間,這個時間被表示成自系統啟動以來的時鐘滴答計數(也即時鐘節拍數)。當一個定時器的expires值小於或等於jiffies變數時,我們就說這個定時器已經超時或到期了。在初始化一個定時器後,通常把它的expires域設定成當前expires變數的當前值加上某個時間間隔值(以時鐘滴答次數計)。 

(3)函式指標function:指向一個可執行函式。當定時器到期時,核心就執行function所指定的函式。而data域則被核心用作function函式的呼叫引數。 

核心函式init_timer()用來初始化一個定時器。實際上,這個初始化函式僅僅將結構中的list成員初始化為空。如下所示(include/linux/timer.h): 


static inline void init_timer(struct timer_list * timer) 

    timer->list.next = timer->list.prev = NULL; 
}
 

由於定時器通常被連線在一個雙向迴圈佇列中等待執行(此時我們說定時器處於pending狀態)。因此函式time_pending()就可以用list成員是否為空來判斷一個定時器是否處於pending狀態。如下所示 


(include/linux/timer.h): 
static inline int timer_pending (const struct timer_list * timer) 

     return timer->list.next != NULL; 
}
 

時間比較操作 

在定時器應用中經常需要比較兩個時間值,以確定timer是否超時,所以Linux核心在timer.h標頭檔案中定義了4個時間關係比較操作巨集。這裡我們說時刻a在時刻b之後,就意味著時間值a≥b。Linux強烈推薦使用者使用它所定義的下列4個時間比較操作巨集(include/linux/timer.h): 


#define time_after(a,b) ((long)(b) - (long)(a) < 0) 
#define time_before(a,b) time_after(b,a) 

#define time_after_eq(a,b) ((long)(a) - (long)(b) >= 0) 
#define time_before_eq(a,b) time_after_eq(b,a)
 
7.6.2 動態核心定時器機制的原理 

Linux是怎樣為其核心定時器機制提供動態擴充套件能力的呢?其關鍵就在於“定時器向量”的概念。所謂“定時器向量”就是指這樣一條雙向迴圈定時器佇列(對列中的每一個元素都是一個timer_list結構):對列中的所有定時器都在同一個時刻到期,也即對列中的每一個timer_list結構都具有相同的expires值。顯然,可以用一個timer_list結構型別的指標來表示一個定時器向量。 

顯然,定時器expires成員的值與jiffies變數的差值決定了一個定時器將在多長時間後到期。在32位系統中,這個時間差值的最大值應該是0xffffffff。因此如果是基於“定時器向量”基本定義,核心將至少要維護0xffffffff個timer_list結構型別的指標,這顯然是不現實的。 

另一方面,從核心本身這個角度看,它所關心的定時器顯然不是那些已經過期而被執行過的定時器(這些定時器完全可以被丟棄),也不是那些要經過很長時間才會到期的定時器,而是那些當前已經到期或者馬上就要到期的定時器(注意!時間間隔是以滴答次數為計數單位的)。 

基於上述考慮,並假定一個定時器要經過interval個時鐘滴答後才到期(interval=expires-jiffies),則Linux採用了下列思想來實現其動態核心定時器機制:對於那些0≤interval≤255的定時器,Linux嚴格按照定時器向量的基本語義來組織這些定時器,也即Linux核心最關心那些在接下來的255個時鐘節拍內就要到期的定時器,因此將它們按照各自不同的expires值組織成256個定時器向量。而對於那些256≤interval≤0xffffffff的定時器,由於他們離到期還有一段時間,因此核心並不關心他們,而是將它們以一種擴充套件的定時器向量語義(或稱為“鬆散的定時器向量語義”)進行組織。所謂“鬆散的定時器向量語義”就是指:各定時器的expires值可以互不相同的一個定時器佇列。 

具體的組織方案可以分為兩大部分: 

(1)對於核心最關心的、interval值在[0,255]之間的前256個定時器向量,核心是這樣組織它們的:這256個定時器向量被組織在一起組成一個定時器向量陣列,並作為資料結構timer_vec_root的一部分,該資料結構定義在kernel/timer.c檔案中,如下述程式碼段所示: 


/* 
* Event timer code 
*/ 
#define TVN_BITS 6 
#define TVR_BITS 8 
#define TVN_SIZE (1 << TVN_BITS) 
#define TVR_SIZE (1 << TVR_BITS) 
#define TVN_MASK (TVN_SIZE - 1) 
#define TVR_MASK (TVR_SIZE - 1) 

struct timer_vec { 
int index; 
struct list_head vec[TVN_SIZE]; 
}; 

struct timer_vec_root { 
int index; 
struct list_head vec[TVR_SIZE]; 
}; 

static struct timer_vec tv5; 
static struct timer_vec tv4; 
static struct timer_vec tv3; 
static struct timer_vec tv2; 
static struct timer_vec_root tv1; 

static struct timer_vec * const tvecs[] = { 
(struct timer_vec *)&tv1, &tv2, &tv3, &tv4, &tv5 
}; 
#define NOOF_TVECS (sizeof(tvecs) / sizeof(tvecs[0]))
 
基於資料結構timer_vec_root,Linux定義了一個全域性變數tv1,以表示核心所關心的前256個定時器向量。這樣核心在處理是否有到期定時器時,它就只從定時器向量陣列tv1.vec[256]中的某個定時器向量內進行掃描。而tv1的index欄位則指定當前正在掃描定時器向量陣列tv1.vec[256]中的哪一個定時器向量,也即該陣列的索引,其初值為0,最大值為255(以256為模)。每個時鐘節拍時index欄位都會加1。顯然,index欄位所指定的定時器向量tv1.vec[index]中包含了當前時鐘節拍內已經到期的所有動態定時器。而定時器向量tv1.vec[index+k]則包含了接下來第k個時鐘節拍時刻將到期的所有動態定時器。當index值又重新變為0時,就意味著核心已經掃描了tv1變數中的所有256個定時器向量。在這種情況下就必須將那些以鬆散定時器向量語義來組織的定時器向量補充到tv1中來。 

(2)而對於核心不關心的、interval值在[0xff,0xffffffff]之間的定時器,它們的到期緊迫程度也隨其interval值的不同而不同。顯然interval值越小,定時器緊迫程度也越高。因此在將它們以鬆散定時器向量進行組織時也應該區別對待。通常,定時器的interval值越小,它所處的定時器向量的鬆散度也就越低(也即向量中的各定時器的expires值相差越小);而interval值越大,它所處的定時器向量的鬆散度也就越大(也即向量中的各定時器的expires值相差越大)。 

核心規定,對於那些滿足條件:0x100≤interval≤0x3fff的定時器,只要表示式(interval>>8)具有相同值的定時器都將被組織在同一個鬆散定時器向量中。因此,為組織所有滿足條件0x100≤interval≤0x3fff的定時器,就需要26=64個鬆散定時器向量。同樣地,為方便起見,這64個鬆散定時器向量也放在一起形成陣列,並作為資料結構timer_vec的一部分。基於資料結構timer_vec,Linux定義了全域性變數tv2,來表示這64條鬆散定時器向量。如上述程式碼段所示。 

對於那些滿足條件0x4000≤interval≤0xfffff的定時器,只要表示式(interval>>8+6)的值相同的定時器都將被放在同一個鬆散定時器向量中。同樣,要組織所有滿足條件0x4000≤interval≤0xfffff的定時器,也需要26=64個鬆散定時器向量。類似地,這64個鬆散定時器向量也可以用一個timer_vec結構來描述,相應地Linux定義了tv3全域性變數來表示這64個鬆散定時器向量。 

對於那些滿足條件0x100000≤interval≤0x3ffffff的定時器,只要表示式(interval>>8+6+6)的值相同的定時器都將被放在同一個鬆散定時器向量中。同樣,要組織所有滿足條件0x100000≤interval≤0x3ffffff的定時器,也需要26=64個鬆散定時器向量。類似地,這64個鬆散定時器向量也可以用一個timer_vec結構來描述,相應地Linux定義了tv4全域性變數來表示這64個鬆散定時器向量。 

對於那些滿足條件0x4000000≤interval≤0xffffffff的定時器,只要表示式(interval>>8+6+6+6)的值相同的定時器都將被放在同一個鬆散定時器向量中。同樣,要組織所有滿足條件0x4000000≤interval≤0xffffffff的定時器,也需要26=64個鬆散定時器向量。類似地,這64個鬆散定時器向量也可以用一個timer_vec結構來描述,相應地Linux定義了tv5全域性變數來表示這64個鬆散定時器向量。 

最後,為了引用方便,Linux定義了一個指標陣列tvecs[],來分別指向tv1、tv2、…、tv5結構變數。如上述程式碼所示。 

7.6.3 核心動態定時器機制的實現 

在核心動態定時器機制的實現中,有三個操作時非常重要的:(1)將一個定時器插入到它應該所處的定時器向量中。(2)定時器的遷移,也即將一個定時器從它原來所處的定時器向量遷移到另一個定時器向量中。(3)掃描並執行當前已經到期的定時器。 

7.6.3.1 動態定時器機制的初始化 

函式init_timervecs()實現對動態定時器機制的初始化。該函式僅被sched_init()初始化例程所呼叫。動態定時器機制初始化過程的主要任務就是將tv1、tv2、…、tv5這5個結構變數中的定時器向量指標陣列vec[]初始化為NULL。如下所示(kernel/timer.c): 


void init_timervecs (void) 

int i; 

for (i = 0; i < TVN_SIZE; i++) { 
     INIT_LIST_HEAD(tv5.vec + i); 
     INIT_LIST_HEAD(tv4.vec + i); 
     INIT_LIST_HEAD(tv3.vec + i); 
     INIT_LIST_HEAD(tv2.vec + i); 

for (i = 0; i < TVR_SIZE; i++) 
     INIT_LIST_HEAD(tv1.vec + i); 
}
 

上述函式中的巨集TVN_SIZE是指timer_vec結構型別中的定時器向量指標陣列vec[]的大小,值為64。巨集TVR_SIZE是指timer_vec_root結構型別中的定時器向量陣列vec[]的大小,值為256。 

7.6.3.2 動態定時器的時鐘滴答基準timer_jiffies 

由於動態定時器是在時鐘中斷的Bottom Half中被執行的,而從TIMER_BH向量被啟用到其timer_bh()函式真正執行這段時間內可能會有幾次時鐘中斷髮生。因此核心必須記住上一次執行定時器機制是什麼時候,也即核心必須儲存上一次執行定時器機制時的jiffies值。為此,Linux在kernel/timer.c檔案中定義了全域性變數timer_jiffies來表示上一次執行定時器機制時的jiffies值。該變數的定義如下所示: 


static unsigned long timer_jiffies;
 

7.6.3.3 對核心動態定時器連結串列的保護 

由於核心動態定時器連結串列是一種系統全域性共享資源,為了實現對它的互斥訪問,Linux定義了專門的自旋鎖timerlist_lock來保護。任何想要訪問動態定時器連結串列的程式碼段都首先必須先持有該自旋鎖,並且在訪問結束後釋放該自旋鎖。其定義如下(kernel/timer.c): 


/* Initialize both explicitly - let's try to have them in the same cache line */ 
spinlock_t timerlist_lock = SPIN_LOCK_UNLOCKED;

7.6.3.4將一個定時器插入到連結串列中

函式add_timer()用來將引數timer指標所指向的定時器插入到一個合適的定時器連結串列中。它首先呼叫timer_pending()函式判斷所指定的定時器是否已經位於在某個定時器向量中等待執行。如果是,則不進行任何操作,只是列印一條核心告警資訊就返回了;如果不是,則呼叫internal_add_timer()函式完成實際的插入操作。其原始碼如下(kernel/timer.c):

Void add_timer(structtimer_list*timer){
   unsignedlongflags;
spin_lock_irqsave(&timerlist_lock,flags);
if(timer_pending(timer))
gotobug;
internal_add_timer(timer);
spin_unlock_irqrestore(&timerlist_lock,flags);
return;
bug:spin_unlock_irqrestore(&timerlist_lock,flags);
printk("bug:kerneltimeraddedtwiceat%p.\n",__builtin_return_address(0));}  

函式internal_add_timer()用於將一個不處於任何定時器向量中的定時器插入到它應該所處的定時器向量中去(根據定時器的expires值來決定)。如下所示(kernel/timer.c):

staticinlinevoidinternal_add_timer(structtimer_list*timer){/**mustbecli-edwhencallingthis*/unsignedlongexpires=timer->expires;unsignedlongidx=expires-timer_jiffies;structlist_head*vec;if(idx<TVR_SIZE){inti=expires&TVR_MASK;vec=tv1.vec i;}elseif(idx<1<<(TVR_BITS TVN_BITS)){inti=(expires>>TVR_BITS)&TVN_MASK;vec=tv2.vec i;}elseif(idx<1<<(TVR_BITS 2*TVN_BITS)){inti=(expires>>(TVR_BITS TVN_BITS))&TVN_MASK;vec=tv3.vec i;}elseif(idx<1<<(TVR_BITS 3*TVN_BITS)){inti=(expires>>(TVR_BITS 2*TVN_BITS))&TVN_MASK;vec=tv4.vec i;}elseif((signedlong)idx<0){/*canhappenifyouaddatimerwithexpires==jiffies,*oryousetatimertogooffinthepast*/vec=tv1.vec tv1.index;}elseif(idx<=0xffffffffUL){inti=(expires>>(TVR_BITS 3*TVN_BITS))&TVN_MASK;vec=tv5.vec i;}else{/*Canonlygethereonarchitectureswith64-bitjiffies*/INIT_LIST_HEAD(&timer->list);return;}/**TimersareFIFO!*/list_add(&timer->list,vec->prev);} 

對該函式的註釋如下:(1)首先,計算定時器的expires值與timer_jiffies的插值(注意!這裡應該使用動態定時器自己的時間基準),這個差值就表示這個定時器相對於上一次執行定時器機制的那個時刻還需要多長時間間隔才到期。區域性變數idx儲存這個差值。(2)根據idx的值確定這個定時器應被插入到哪一個定時器向量中。其具體的確定方法我們在7.6.2節已經說過了,這裡不再詳述。最後,定時器向量的頭部指標vec表示這個定時器應該所處的定時器向量連結串列頭部。(3)最後,呼叫list_add()函式將定時器插入到vec指標所指向的定時器佇列的尾部。

7.6.3.5修改一個定時器的expires值當一個定時器已經被插入到核心動態定時器連結串列中後,我們還可以修改該定時器的expires值。函式mod_timer()實現這一點。如下所示(kernel/timer.c):

intmod_timer(structtimer_list*timer,unsignedlongexpires){intret;unsignedlongflags;spin_lock

該函式首先根據引數expires值更新定時器的expires成員。然後呼叫detach_timer()函式將該定時器從它原來所屬的連結串列中刪除。最後呼叫internal_add_timer()函式將該定時器根據它新的expires值重新插入到相應的連結串列中。函式detach_timer()首先呼叫timer_pending()來判斷指定的定時器是否已經處於某個連結串列中,如果定時器原來就不處於任何連結串列中,則detach_timer()函式什麼也不做,直接返回0值,表示失敗。否則,就呼叫list_del()函式將定時器從它原來所處的連結串列中摘除。如下所示(kernel/timer.c):

staticinlineintdetach_timer(structtimer_list*timer){if(!timer_pending(timer))return0;list_del(&timer->list);return1;}  

7.6.3.6刪除一個定時器函式del_timer()用來將一個定時器從相應的核心定時器佇列中刪除。該函式實際上是對detach_timer()函式的高層封裝。如下所示(kernel/timer.c):

intdel_timer(structtimer_list*timer){intret;unsignedlongflags;spin_lock_irqsave(&timerlist_lock,flags);ret=detach_timer(timer);timer->list.next=timer->list.prev=NULL;spin_unlock_irqrestore(&timerlist_lock,flags);returnret;} 

軟體開發網

7.6.3.7定時器遷移操作由於一個定時器的interval值會隨著時間的不斷流逝(即jiffies值的不斷增大)而不斷變小,因此那些原本到期緊迫程度較低的定時器會隨著jiffies值的不斷增大而成為既將馬上到期的定時器。比如定時器向量tv2.vec[0]中的定時器在經過256個時鐘滴答後會成為未來256個時鐘滴答內會到期的定時器。因此,定時器在核心動態定時器連結串列中的位置也應相應地隨著改變。改變的規則是:當tv1.index重新變為0時(意味著tv1中的256個定時器向量都已被核心掃描一遍了,從而使tv1中的256個定時器向量變為空),則用tv2.vec[index]定時器向量中的定時器去填充tv1,同時使tv2.index加1(它以64為模)。當tv2.index重新變為0(意味著tv2中的64個定時器向量都已經被全部填充到tv1中去了,從而使得tv2變為空),則用tv3.vec[index]定時器向量中的定時器去填充tv2。如此一直類推下去,直到tv5。函式cascade_timers()完成這種定時器遷移操作,該函式只有一個timer_vec結構型別指標的引數tv。這個函式將把定時器向量tv->vec[tv->index]中的所有定時器重新填充到上一層定時器向量中去。如下所示(kernel/timer.c):

staticinlinevoidcascade_timers(structtimer_vec*tv){/*cascadeallthetimersfromtvuponelevel*/structlist_head*head,*curr,*next;head=tv->vec tv->index;curr=head->next;/**Weareremoving_all_timersfromthelist,sowedon'thaveto*detachthemindividually,justclearthelistafterwards.*/while(curr!=head){structtimer_list*tmp;tmp=list_entry(curr,structtimer_list,list);next=curr->next;list_del(curr);//notneededinternal_add_timer(tmp);curr=next;}INIT_LIST_HEAD(head);tv->index=(tv->index 1)&TVN_MASK;}  

對該函式的註釋如下:(1)首先,用指標head指向定時器頭部向量頭部的list_head結構。指標curr指向定時器向量中的第一個定時器。

(2)然後,用一個while{}迴圈來遍歷定時器向量tv->vec[tv->index]。由於定時器向量是一個雙向迴圈佇列,因此迴圈的終止條件是curr=head。對於每一個被掃描的定時器,迴圈體都先呼叫list_del()函式將當前定時器從連結串列中摘除,然後呼叫internal_add_timer()函式重新確定該定時器應該被放到哪個定時器向量中去。(3)當從while{}迴圈退出後,定時器向量tv->vec[tv->index]中所有的定時器都已被遷移到其它地方(到它們該呆的地方:-),因此它本身就成為一個空佇列。這裡我們顯示地呼叫INIT_LIST_HEAD()巨集來將定時器向量的表頭結構初始化為空。(4)最後,將tv->index值加1,當然它是以64為模。

7.6.4.8掃描並執行當前已經到期的定時器函式run_timer_list()完成這個功能。如前所述,該函式是被timer_bh()函式所呼叫的,因此核心定時器是在時鐘中斷的BottomHalf中被執行的。記住這一點非常重要。全域性變數timer_jiffies表示了核心上一次執行run_timer_list()函式的時間,因此jiffies與timer_jiffies的差值就表示了自從上一次處理定時器以來,期間一共發生了多少次時鐘中斷,顯然run_timer_list()函式必須為期間所發生的每一次時鐘中斷補上定時器服務。該函式的原始碼如下(kernel/timer.c):

staticinlinevoidrun_timer_list(void){spin_lock_irq(&timerlist_lock);while((long)(jiffies-timer_jiffies)>=0){structlist_head*head,*curr;if(!tv1.index){intn=1;do{cascade_timers(tvecs[n]);}while(tvecs[n]->index==1&&  n<NOOF_TVECS);}repeat:head=tv1.vec tv1.index;curr=head->next;if(curr!=head){structtimer_list*timer;void(*fn)(unsignedlong);unsignedlongdata;timer=list_entry(curr,structtimer_list,list);fn=timer->function;data=timer->data;detach_timer(timer);timer->list.next=timer->list.prev=NULL;timer_enter(timer);spin_unlock_irq(&timerlist_lock);fn(data);spin_lock_irq(&timerlist_lock);timer_exit();gotorepeat;}  timer_jiffies;tv1.index=(tv1.index 1)&TVR_MASK;}spin_unlock_irq(&timerlist_lock);}  

函式run_timer_list()的執行過程主要就是用一個大while{}迴圈來為時鐘中斷執行定時器服務,每一次迴圈服務一次時鐘中斷。因此一共要執行(jiffies-timer_jiffies+1)次迴圈。迴圈體所執行的服務步驟如下:(1)首先,判斷tv1.index是否為0,如果為0則需要從tv2中補充定時器到tv1中來。但tv2也可能為空而需要從tv3中補充定時器,因此用一個do{}while迴圈來呼叫cascade_timer()函式來依次視需要從tv2中補充tv1,從tv3中補充tv2、…、從tv5中補充tv4。顯然如果tvi.index=0(2≤i≤5),則對於tvi執行cascade_timers()函式後,tvi.index肯定為1。反過來講,如果對tvi執行過cascade_timers()函式後tvi.index不等於1,那麼可以肯定在未對tvi執行cascade_timers()函式之前,tvi.index值肯定不為0,因此這時tvi不需要從tv(i 1)中補充定時器,這時就可以終止do{}while迴圈。(2)接下來,就要執行定時器向量tv1.vec[tv1.index]中的所有到期定時器。因此這裡用一個gotorepeat迴圈從頭到尾依次掃描整個定時器對列。由於在執行定時器的關聯函式時並不需要關CPU中斷,所以在用detach_timer()函式將當前定時器從對列中摘除後,就可以呼叫spin_unlock_irq()函式進行解鎖和開中斷,然後在執行完當前定時器的關聯函式後重新用spin_lock_irq()函式加鎖和關中斷。

(2)然後,用一個while{}迴圈來遍歷定時器向量tv->vec[tv->index]。由於定時器向量是一個雙向迴圈佇列,因此迴圈的終止條件是curr=head。對於每一個被掃描的定時器,迴圈體都先呼叫list_del()函式將當前定時器從連結串列中摘除,然後呼叫internal_add_timer()函式重新確定該定時器應該被放到哪個定時器向量中去。(3)當從while{}迴圈退出後,定時器向量tv->vec[tv->index]中所有的定時器都已被遷移到其它地方(到它們該呆的地方:-),因此它本身就成為一個空佇列。這裡我們顯示地呼叫INIT_LIST_HEAD()巨集來將定時器向量的表頭結構初始化為空。(4)最後,將tv->index值加1,當然它是以64為模。

7.6.4.8掃描並執行當前已經到期的定時器函式run_timer_list()完成這個功能。如前所述,該函式是被timer_bh()函式所呼叫的,因此核心定時器是在時鐘中斷的BottomHalf中被執行的。記住這一點非常重要。全域性變數timer_jiffies表示了核心上一次執行run_timer_list()函式的時間,因此jiffies與timer_jiffies的差值就表示了自從上一次處理定時器以來,期間一共發生了多少次時鐘中斷,顯然run_timer_list()函式必須為期間所發生的每一次時鐘中斷補上定時器服務。該函式的原始碼如下(kernel/timer.c):

static inline void run_timer_list(void){spin_lock_irq(&timerlist_lock);while((long)(jiffies-timer_jiffies)>=0){structlist_head*head,*curr;if(!tv1.index){intn=1;do{cascade_timers(tvecs[n]);}while(tvecs[n]->index==1&&  n<NOOF_TVECS);}repeat:head=tv1.vec tv1.index;curr=head->next;if(curr!=head){structtimer_list*timer;void(*fn)(unsignedlong);unsignedlongdata;timer=list_entry(curr,structtimer_list,list);fn=timer->function;data=timer->data;detach_timer(timer);timer->list.next=timer->list.prev=NULL;timer_enter(timer);spin_unlock_irq(&timerlist_lock);fn(data);spin_lock_irq(&timerlist_lock);timer_exit();gotorepeat;}  timer_jiffies;tv1.index=(tv1.index 1)&TVR_MASK;}spin_unlock_irq(&timerlist_lock);}  

函式run_timer_list()的執行過程主要就是用一個大while{}迴圈來為時鐘中斷執行定時器服務,每一次迴圈服務一次時鐘中斷。因此一共要執行(jiffies-timer_jiffies+1)次迴圈。迴圈體所執行的服務步驟如下:(1)首先,判斷tv1.index是否為0,如果為0則需要從tv2中補充定時器到tv1中來。但tv2也可能為空而需要從tv3中補充定時器,因此用一個do{}while迴圈來呼叫cascade_timer()函式來依次視需要從tv2中補充tv1,從tv3中補充tv2、…、從tv5中補充tv4。顯然如果tvi.index=0(2≤i≤5),則對於tvi執行cascade_timers()函式後,tvi.index肯定為1。反過來講,如果對tvi執行過cascade_timers()函式後tvi.index不等於1,那麼可以肯定在未對tvi執行cascade_timers()函式之前,tvi.index值肯定不為0,因此這時tvi不需要從tv(i 1)中補充定時器,這時就可以終止do{}while迴圈。(2)接下來,就要執行定時器向量tv1.vec[tv1.index]中的所有到期定時器。因此這裡用一個gotorepeat迴圈從頭到尾依次掃描整個定時器對列。由於在執行定時器的關聯函式時並不需要關CPU中斷,所以在用detach_timer()函式將當前定時器從對列中摘除後,就可以呼叫spin_unlock_irq()函式進行解鎖和開中斷,然後在執行完當前定時器的關聯函式後重新用spin_lock_irq()函式加鎖和關中斷。