kernel timer

參考資料出自:

http://blog.csdn.net/mengzhuicanyang/article/details/6955811

http://blog.163.com/xdd123www@126/blog/static/75475147200849113819515/

內核定時器(也稱爲動態定時器)是內核在以後某一個時刻運行一段程序或進程的基礎,軟件定時器可以在一個確切的時間點上(更嚴格地說是一個時間點以後)激活相應的程序段或進程。軟件定時器在設備驅動程序中被大量應用以檢測設備的狀態。

使用一個軟件定時器很簡單,只需做一些初始化工作,設置一個相對於當前時刻的超時時間和超時處理函數,將其插入到內核定時器隊列中即可,設置的超時處理函數會在定時器超時時自動運行。下面介紹如何使用內核定時器和實現內核定時器的內部架構。

1.內核定時器的使用方法

內核定時器由數據結構timer_list表示,該結構表示了一個待處理的延遲任務,我們稱該數據結構爲內核定時器節點。該數據結構的詳細內容請看下面的代碼清單。

代碼清單--數據結構timer_list

功能簡介:該數據結構保存了內核定時器節點的相關信息,包括定時器超時時間和超時處理函數等。

文件:src/include/Linux/timer.h
11  struct timer_list {    
12       struct list_head entry;
13       unsigned long expires;
14       void (*function)(unsigned long); 
15       unsigned long data;    
16       struct timer_base_s *base;  
};

成員變量entry:該內核鏈表表頭類型成員變量用於將該內核定時器節點連接到系統中的定時器鏈表中。

成員變量expires:該無符號長整型變量保存了該定時器的超時時間,用於和系統核心變量jiffies進行比較。

成員變量function:該函數指針變量保存了內核定時器超時後要執行的函數,即定時器超時處理函數。

成員變量data:該無符號長整型變量用作定時器超時處理函數的參數。

成員變量base:該指針變量表明瞭該內核定時器節點歸屬於系統中哪一個處理器,在使用函數init_timer()初始化內核定時

器節點的過程中,將該指針指向了一個每處理器變量tvec_bases的成員變量t_base。

在瞭解了內核定時器節點數據結構的相關內容之後,下面來看如何在自己的代碼中使用一個內核定時器節點,實現在一段時間後執行一個延遲處理任務。

①首先,使用下面語句聲明一個內核定時器數據結構。

struct timer_list my_timer;

②使用函數init_timer()對上一步聲明的內核定時器結構進行初始化。函數init_timer()主要設置該內核定時器歸屬系統中哪一個處理,並初始化內核定時器鏈表指針的next域爲NULL。

init_timer(&my_timer);

③使用下面的語句來設置內核定時器的超時時間expires、超時處理函數function、超時處理函數所使用的參數data。

my_timer.expires = jiffies + delay; 
my_timer.data = 0;     
my_timer.function = my_function;

④也是最後一步,通過函數add_timer()來激活內核定時器,使用的語句如下:

add_timer(&my_timer);

通過上面4步,我們就創建了一個內核定時器節點my_timer。該內核定時器在當前時刻以後delay個時鐘中斷後超時,執行超時處理函數my_function,傳給超時處理函數的參數爲0。

除了上述過程中介紹的內核定時器接口函數之外,內核同時提供了以下接口函數來輔助對內核定時器的操作。

intmod_timer(structtimer_list*timer,unsignedlongexpires):該函數負責修改內核定時器timer的超時字段expires。該函數可以修改激活和沒有激活的內核定時器的超時時間,並把它們都設置爲激活狀態;返回值爲0表示修改的內核定時器在修改之前處於未激活狀態,返回值爲1表示修改的內核定時器在修改之前處於已激活狀態。

intdel_timer(structtimer_list*timer)、intdel_timer_sync(structtimer_list*timer):這兩個函數負責從鏈表中刪除內核定時器timer。它們的區別在於,後者在多處理器系統中會確保其他處理器上沒有處理或者處理完畢當前內核定時器timer時才退出。

2.內核定時器架構

與softirq、工作隊列兩種中斷下半部的處理方法類似,每一個內核定時器節點與系統中的處理器通過一個每處理器變量聯繫起來。內核在文件src/kernel/timer.c中的第88行使用下面的語句分配了一個名稱爲tvec_bases、類型爲tvec_base_t的每處理器變量。

static DEFINE_PER_CPU(tvec_base_t, tvec_bases);

其中,tvec_base_t是數據結構structtvec_t_base_s通過語句typedef定義的一個別名,數據結構structtvec_t_base_s用來記錄系統中每一個處理器上待處理內核定時器節點的相關信息。有關該數據結構的詳細內容請看下面的代碼清單。

代碼清單--數據結構tvec_t_base_s

功能簡介:該數據結構用於有效組織當前處理器上所有待處理內核定時器節點,以支持快速訪問超時內核定時器節點。

該數據結構在同一文件中的第77行開始定義,代碼如下。接下來是對這些成員用途的分析、說明。

struct tvec_t_base_s {
struct timer_base_s t_base;
unsigned long timer_jiffies;
tvec_root_t tv1;
tvec_t tv2;
tvec_t tv3;
tvec_t tv4;
tvec_t tv5;
} ____cacheline_aligned_in_smp;

成員變量t_base的數據類型爲structtimer_base_s,它在文件src/kernel/timer.c中的第64行開始定義,代碼如下:

truct timer_base_s {
spinlock_t lock;
struct timer_list *running_timer;
);

其中,成員變量running_timer記錄了正在本地處理器上進行超時處理的內核定時器;另外一個自旋鎖成員變量lock用於保護每處理器變量tvec_bases的本地拷貝。

無符號長整型變量timer_jiffies記錄了該數據結構中所包含的定時器中最早超時時間,根據該變量可以計算出超時定時器節點所在鏈表的表頭。

變量tv1的類型爲tvec_root_t,該數據結構在文件src/kernel/timer.c中的第73行開始定義,代碼如下。該數據結構中包含了TVR_SIZE個鏈表表頭指針。

typedef struct tvec_root_s {
struct list_head vec[TVR_SIZE];
} tvec_root_t;

變量tv2、tv3、tv4、tv5的類型爲tvec_t,該數據結構在文件src/kernel/timer.c中的第69行開始定義,代碼如下:

ypedef struct tvec_s {
struct list_head vec[TVN_SIZE];
)tvec_t;
該數據結構中包含了TVN_SIZE個鏈表表頭指針,其中TVR_SIZE、TVN_SIZE的值在沒有選中內核選項CONFIG_BASE_SMALL時分別爲256、64;否則分別爲64、16。內核選項CONFIG_BASE_SMALL用於爲系統資源匱乏的計算機系統進行優化,選中該內核選項後,可以減小系統核心對內存的使用量。通常個人計算機中不會選中該內核選項。

這5個變量一共包含了256+64×4=512個鏈表表頭,每個鏈表表頭指針指向了一個待處理內核定時器鏈表。由當前處理器處理的內核定時器根據其超時時間的不同分佈在這512個鏈表上。通過上面的分析,可以用一個形象化的示意圖來描述內核定時器的架構,如圖所示。

212450100.jpg
內核定時器架構示意圖

定時器的使用非常方便,只需要執行一些初始化的操作,設置一個超時時間,指定超時發生時執行的函數,然後激活定時器就可以了。它的處理和工作隊列還是有點類似的。其實,在Linux內核開發中,很多的操作都是類似的。還有一點需要注意的,內核定時器並不是週期運行,它在超時後自動銷燬。因此,如果要實現週期輪詢,就需要在定時器執行函數返回前再次激活定時器
下面看看一個實現輪詢操作的小例子:


structtimer_listpolling_timer;

init_timer(&polling_timer);
polling_timer.data=(unsignedlong)something;
polling_timer.function=polling_handler;
polling_timer.expires=jiffies+2*HZ;
add_timer(&polling_timer);

voidpolling_handler(unsignedlongdata){
...
polling_timer.expires=jiffies+2*HZ;
add_timer(&polling_timer);
}

jiffies是Linux內核中的一個全局變量,用來記錄自系統啓動以來產生的節拍的總數。啓動時,內核將該變量初始化爲0,此後,每次時鐘中斷處理程序都會增加該變量的值。
HZ是內核定義的宏,在i386體系結構中定義爲:
#defineHZ1000
2.6內核的時鐘中斷頻率是1000,也就是說,在1秒裏jiffies會被增加1000。因此jiffies+2*HZ表示推後2秒鐘。
有時,需要更改已經激活的定時器,採用如下函數:
mod_timer(&polling_timer,jiffies+new_delay);
如果需要在定時器超時前停止定時器,可以使用del_timer()函數:
del_timer(&polling_timer);
在多處理器的情況下使用:
del_timer_sync(&polling_timer);
注意,不需要爲已經超時的定時器調用該函數,因爲它們會自動被刪除。
內核定時器是在時鐘中斷髮生後,作爲軟中斷在下半部的上下文鍾執行的。所有的定時器結構都以鏈表的形式存儲。時鐘中斷髮生後,內核按鏈表順序依次執行。一般來說,定時器在超時後會立即執行,但是也有可能被推遲到下一個時鐘節拍才能運行,所以不能用定時器來實現硬實時的操作。又因爲內核定時器發生在軟中斷中,因此,定時器執行函數不能夠睡眠,也不能夠持有信號量。如果對硬件的訪問需要使用信號量同步,或者可能睡眠(比如需要調用kmalloc內存分配,但是由於某種原因不能使用GFP_ATOMIC標誌),就不能直接通過定時器來實現了。一個變通的做法是在內核定時器執行函數裏調用工作隊列,在工作隊列處理函數中實現對硬件的訪問。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章