標籤:
轉自:http://www.cnblogs.com/hoys/archive/2011/11/14/2248586.html
計時器是所有作業系統的一個必要組成部分,您將發現多個計時器機制。我們將首先簡要介紹一些 Linux 計時器模式,然後深入研究它們的運行方式。
(Linux)時間的起源
在 Linux 核心中,時間由一個名為 jiffies
的全域變數衡量,該變數標識系統啟動以來經過的滴答數。在最低的層級上,計算滴答數的方式取決於正在啟動並執行特定硬體平台;但是,滴答計數通常在一次中斷期間仍然繼續進行。滴答速率(jiffies
的最不重要的位)可以配置,但在最近針對 x86 的 2.6 核心中,一次滴答等於 4ms(250Hz)。jiffies
全域變數在核心中廣泛使用,目的有幾個,其中之一是提供用於計算一個計時器的逾時值的當前絕對時間(稍後將展示一個例子)。
回頁首
核心計時器
最近的 2.6 核心中有幾個不同的計時器模式,其中最簡單、最不精確(但適用於大多數執行個體)的模式就是計時器 API。這個 API 允許構造在 jiffies
域(最低 4ms 逾時)中啟動並執行計時器。還有一個高精確度計時器 API,它允許構造在以納秒定義的時間中啟動並執行計時器。根據您的處理器和處理器啟動並執行速度,您的裡程(mileage)可能會不同,但這個 API 的確提供了一種方法來在 jiffies
滴答間隔下調度逾時。
標準計時器
標準計時器 API 作為 Linux 核心的一部分已經有很長一段時間了(自從 Linux 核心的早期版本開始)。儘管它提供的精確性比高精確度計時器要低,但它對於在處理物理裝置時提供錯誤覆蓋的傳統驅動程式逾時來說比較理想。在很多情況下,這些逾時實際上從不觸發,而是被啟動,然後被刪除。
簡單核心計時器使用計時器輪(timer wheel) 實現。這個主意是由 Finn Arne Gangstad 在 1997 年首次引入的。它不理睬管理大量計時器的問題,而是很好地管理數量合理的計時器 — 典型情況。(原始計時器實現只是按照到期順序將計時器實現雙重連結。儘管在概念上比較簡單,但這種方法是不可伸縮的。)時間輪是一個 buckets 集合,其中每個 bucker 表示將來計時器到期的一個時間塊。這些 buckets 使用基於 5 個 bucket 的對數時間定義。使用 jiffies
作為時間粒紋,定義了幾個組,它們表示將來的到期時段(其中每個組通過一列計時器表示)。計時器插入使用具有 O(1) 複雜度的列表操作發生,到期發生在 O(N) 時間內。計時器到期以串聯的形式出現,其中計時器被從高粒度 buckets 刪除,然後隨著它們的到期時間的下降被插入到低粒度 buckets 中。現在我們查看一下針對這個計時器實現的 API。
計時器 API
Linux 提供了一個簡單的 API 來構造和管理計時器。它包含一些函數(和助手函數),用於建立、取消和管理計時器。
計時器通過 timer_list
結構定義,該結構包括實現一個計時器所需的所有資料(其中包括列表指標和在編譯時間配置的可選計時器統計資料)。從使用者角度看,timer_list
包含一個到期時間,一個回呼函數(當/如果計時器到期),以及一個使用者提供的上下文。使用者必須初始化計時器,可以採取幾種方法,最簡單的方法是調用 setup_timer
,該函數初始化計時器並設定使用者提供的回呼函數和上下文。或者,使用者可以設定計時器中的這些值(函數和資料)並簡單地調用 init_timer
。注意,init_timer
由 setup_timer
內部調用。
void init_timer( struct timer_list *timer );void setup_timer( struct timer_list *timer, void (*function)(unsigned long), unsigned long data ); |
擁有一個經過初始化的計時器之後,使用者現在需要設定到期時間,這通過調用 mod_timer
來完成。由於使用者通常提供一個未來的到期時間,他們通常在這裡添加 jiffies
來從目前時間位移。使用者也可以通過調用 del_timer
來刪除一個計時器(如果它還沒有到期):
int mod_timer( struct timer_list *timer, unsigned long expires );void del_timer( struct timer_list *timer ); |
最後,使用者可以通過調用 timer_pending
(如果正在等待,將返回 1
)來發現計時器是否正在等待(還沒有發出):
int timer_pending( const struct timer_list *timer ); |
計時器樣本
我們來檢查一下這些 API 函數的實際運行情況。清單 1 提供了一個簡單的核心模組,用於展示簡單計時器 API 的核心特點。在 init_module
中,您使用 setup_timer
初始化了一個計時器,然後調用 mod_timer
來啟動它。當計時器到期時,將調用回呼函數 my_timer_callback
。最後,當您刪除模組時,計時器刪除(通過 del_timer
)發生。(注意來自 del_timer
的返回檢查,它確定計時器是否還在使用。)
清單 1. 探索簡單計時器 API
#include <linux/kernel.h>#include <linux/module.h>#include <linux/timer.h>MODULE_LICENSE("GPL");static struct timer_list my_timer;void my_timer_callback( unsigned long data ){ printk( "my_timer_callback called (%ld).\n", jiffies );}int init_module( void ){ int ret; printk("Timer module installing\n"); // my_timer.function, my_timer.data setup_timer( &my_timer, my_timer_callback, 0 ); printk( "Starting timer to fire in 200ms (%ld)\n", jiffies ); ret = mod_timer( &my_timer, jiffies + msecs_to_jiffies(200) ); if (ret) printk("Error in mod_timer\n"); return 0;}void cleanup_module( void ){ int ret; ret = del_timer( &my_timer ); if (ret) printk("The timer is still in use...\n"); printk("Timer module uninstalling\n"); return;} |
您可以在 ./include/linux/timer.h 中進一步瞭解計時器 API。儘管簡單計時器 API 簡單有效,但它並不能提供即時應用程式所需的準確性。為此,我們來看一下 Linux 最近新增的功能,該功能用於支援精確度更高的計時器。
高精確度計時器
高精確度計時器(簡稱 hrtimers)提供一個高精確度的計時器管理架構,這個架構獨立於此前討論過的計時器架構,原因是合并這兩個架構太複雜。儘管計時器在 jiffies
粒度上運行,hrtimers 在納秒粒度上運行。
hrtimer 架構的實現方式與傳統計時器 API 不同。hrtimer 不使用 buckets 和串聯操作,而是維護一個按時間排序的計時器資料結構(按時間順序插入計時器,以最小化啟用時的處理)。這個資料結構是一個 “紅-黑” 樹,對於注重效能的應用程式很理想(且恰好作為核心中的一個庫普遍可用)。
hrtimer 架構作為核心中的一個 API 可用,使用者空間應用程式也可以通過 nanosleep
、itimers
和 Portable Operating System Interface (POSIX)-timers interface 使用它。hrtimer 架構被主線化(mainlined)到 2.6.21 核心中。
高精確度計時器 API
hrtimer API 與傳統 API 有些相似,但它們之間的一些根本差別是它能夠進行額外的時間控制。應該注意的第一點是:時間不是用 jiffies
表示的,而是以一種名為 ktime
的特殊資料類型表示。這種表示方法隱藏了在這個粒度上有效行政時間的一些細節。hrtimer API 正式確認(formalize)了絕對時間和相對時間之間的區別,要求調用者指定類型。
與傳統的計時器 API 類似,高精確度計時器通過一個結構表示 — 這裡是 hrtimer
。這個結構從使用者角度定義定時器(回呼函數、到期時間等)並包含了管理資訊(其中計時器存在於 “紅-黑” 樹、可選統計資料等中)。
定義過程首先通過 hrtimer_init
初始化一個計時器。這個調用包含計時器、時鐘定義和計時器模式(one-shot 或 restart)。使用的時鐘在 ./include/linux/time.h 中定義,表示系統支援的各種時鐘(比如系統時鐘或者單一時鐘,後者只表示從一個起點[比如系統啟動]開始的時間)。計時器被初始化之後,就可以通過 hrtimer_start
啟動。這個調用包含到期時間(在 ktime_t
中)和時間值的模式(絕對或相對值)。
void hrtimer_init( struct hrtimer *time, clockid_t which_clock, enum hrtimer_mode mode );int hrtimer_start(struct hrtimer *timer, ktime_t time, const enum hrtimer_mode mode); |
hrtimer 啟動後,可以通過調用 hrtimer_cancel
或 hrtimer_try_to_cancel
來取消。每個函數都包含將被停止的計時器的 hrtimer 引用。這兩個函數的區別在於:hrtimer_cancel
函數試圖取消計時器,但如果計時器已經發出,那麼它將等待回呼函數結束;hrtimer_try_to_cancel
函數也試圖取消計時器,但如果計時器已經發出,它將返回失敗。
int hrtimer_cancel(struct hrtimer *timer);int hrtimer_try_to_cancel(struct hrtimer *timer); |
可以通過調用 hrtimer_callback_running
來檢查 hrtimer 是否已經啟用它的回呼函數。注意,這個函數由 hrtimer_try_to_cancel
內部調用,以便在計時器的回呼函數被調用時返回一個錯誤。
int hrtimer_callback_running(struct hrtimer *timer); |
ktime API
本文沒有討論 ktime API,它提供一組豐富的函數來以較高的精確度行政時間。可以在 ./linux/include/ktime.h 中查看 ktime API。
一個 hrtimer 樣本
hrtimer API 的使用方法非常簡單,如 清單 2 所示。在 init_module
中,首先定義針對逾時的相對時間(本例中為 200ms)。然後,通過調用 hrtimer_init
來初始化您的 hrtimer(使用單一時鐘),並設定回呼函數。最後,使用此前建立的 ktime
值啟動計時器。當計時器發出時,將調用 my_hrtimer_callback
函數,該函數返回 HRTIMER_NORESTART
,以避免計時器自動重新啟動。在 cleanup_module
函數中,通過調用 hrtimer_cancel
來取消計時器。
清單 2. 探索 hrtimer API
#include <linux/kernel.h>#include <linux/module.h>#include <linux/hrtimer.h>#include <linux/ktime.h>MODULE_LICENSE("GPL");#define MS_TO_NS(x)(x * 1E6L)static struct hrtimer hr_timer;enum hrtimer_restart my_hrtimer_callback( struct hrtimer *timer ){ printk( "my_hrtimer_callback called (%ld).\n", jiffies ); return HRTIMER_NORESTART;}int init_module( void ){ ktime_t ktime; unsigned long delay_in_ms = 200L; printk("HR Timer module installing\n"); ktime = ktime_set( 0, MS_TO_NS(delay_in_ms) ); hrtimer_init( &hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL ); hr_timer.function = &my_hrtimer_callback; printk( "Starting timer to fire in %ldms (%ld)\n", delay_in_ms, jiffies ); hrtimer_start( &hr_timer, ktime, HRTIMER_MODE_REL ); return 0;}void cleanup_module( void ){ int ret; ret = hrtimer_cancel( &hr_timer ); if (ret) printk("The timer was still in use...\n"); printk("HR Timer module uninstalling\n"); return;} |
關於 hrtimer API,還有許多內容這裡沒有涉及到。一個有趣的方面是它能夠定義回呼函數的執行內容(比如在 softirq 或 hardiirq 上下文中)。您可以在 ./include/linux/hrtimer.h 檔案中進一步瞭解 hrtimer API。
回頁首
核心列表
如本文此前所述,列表是有用的結構,核心提供了一個有效通用使用實現。另外,您將在我們此前討論過的 APIs 下面發現列表。理解這個雙重連結的列表 API 有助於使用這個有效資料結構進行開發,您會發現,代碼在這個利用列表的核心中是多餘的。現在我們來快速瞭解一下這個核心列表 API。
這個 API 提供一個 list_head
結構,用於表示列表頭(錨點)和結構內(in-structure)列表指標。我們來檢查一個包含列表功能的範例結構(參見 清單 3)。注意,清單 3 添加了 list_head
結構,該結構用於對象連結(object linkage)。注意,可以在您的結構中的任意位置添加這個 list_head
結構 — 通過一些 GCC(list_entry
和 container_of
,在 ./include/kernel/kernel.h 中定義)— 可以取消從列表指標到超對象的引用。
清單 3. 帶有列表引用的範例結構
struct my_data_structure {int value;struct list_head list;}; |
與其他列表實現一樣,需要一個列表頭來充當列表的錨點。這通常通過 LIST_HEAD
宏來完成,這個宏提供列表的聲明和初始化。這個宏建立一個結構 list_head
對象,可以在該對象上添加其他一些對象。
也可以通過使用 LIST_HEAD_INIT
宏手動建立一個列表頭(例如,您的列表頭位於另一個結構中)。
主初始化完成後,可以使用 list_add
和 list_del
等函數來操縱列表。下面,我們將跳到範例程式碼,以便更好地解釋這個 API 的使用方法。
列表 API 樣本
清單 4 提供一個簡單的核心模組來探索幾個列表 AIO 函數(./include/linux/list.h 中包含更多函數)。這個樣本建立了兩個列表,使用 init_module
函數來填充它們,然後使用 cleanup_module
函數來操縱這兩個列表。
一開始,您建立了您的資料結構(my_data_struct
),該結構包含一些資料和兩個列表頭。這個樣本展示可以將一個對象同時插入到多個列表中。然後,您建立了兩個列表頭(my_full_list
和 my_odd_list
)。
在 init_module
函數中,您建立了 10 個資料對象,並使用 list_add
函數將它們載入到列表中(所有對象載入到 my_full_list
中,所有奇值對象載入到 my_odd_list
) 中)。這裡要注意一點:list_add
接受兩個參數,一個是將用到的對象中的列表引用,另一個是列表錨點。這個樣本展示了將一個資料對象插入多個列表的能力,方法是使用核心的內部機制來識別包含列表引用的超級對象。
cleanup_module
函數提供了這個 API 的其他幾個功能,其中之一是 list_for_each
宏,這個宏簡化了列表迭代。對於這個宏,您提供了一個對當前對象(pos
)的引用以及將被迭代的列表引用。對於每次迭代,您接收一個 list_head
引用,list_entry
接收這個引用以識別容器物件(您的資料結構)。指定您的結構和結構之內的列表變數,後者用於在內部取值 (Dereference),返回容器。
為發出奇值列表(odd list),要使用另一個名為 list_for_each_entry
的迭代宏。這個宏更簡單,因為它自動提供資料結構,無需再執行一個 list_entry
函數。
最後,使用 list_for_each_safe
來迭代列表,以便釋放已指派的元素。這個宏將迭代列表,但阻止刪除列表條目(刪除列表條目是迭代操作的一部分)。您使用 list_entry
來擷取您的資料對象(以便將它釋放回核心池),然後使用 list_del
來釋放列表中的條目。
清單 4. 探索列表 API
#include <linux/kernel.h>#include <linux/module.h>#include <linux/list.h>MODULE_LICENSE("GPL");struct my_data_struct { int value; struct list_head full_list; struct list_head odd_list;};LIST_HEAD( my_full_list );LIST_HEAD( my_odd_list );int init_module( void ){ int count; struct my_data_struct *obj; for (count = 1 ; count < 11 ; count++) { obj = (struct my_data_struct *) kmalloc( sizeof(struct my_data_struct), GFP_KERNEL ); obj->value = count; list_add( &obj->full_list, &my_full_list ); if (obj->value & 0x1) { list_add( &obj->odd_list, &my_odd_list ); } } return 0;}void cleanup_module( void ){ struct list_head *pos, *q; struct my_data_struct *my_obj; printk("Emit full list\n"); list_for_each( pos, &my_full_list ) { my_obj = list_entry( pos, struct my_data_struct, full_list ); printk( "%d\n", my_obj->value ); } printk("Emit odd list\n"); list_for_each_entry( my_obj, &my_odd_list, odd_list ) { printk( "%d\n", my_obj->value ); } printk("Cleaning up\n"); list_for_each_safe( pos, q, &my_full_list ) { struct my_data_struct *tmp; tmp = list_entry( pos, struct my_data_struct, full_list ); list_del( pos ); kfree( tmp ); } return;} |
還有很多其他函數,它們的用途包括在列表末尾而不是頭部添加資料(list_add_tail
)、串連列表(list_splice
)、測試清單的內容(list_empty
)等。請參見 參考資料 詳細瞭解核心列表函數。
回頁首
結束語
本文探索了幾個 API,展示了在必要時隔離功能的能力(計時器 API 和高精確度 hrtimer API)以及編寫通用代碼以實現代碼重用的能力(列表 API)。傳統計時器為典型的驅動程式逾時提供了一種有效機制,而 hrtimer 為更精確的計時器功能提供了更高水平的服務品質。列表 API 提供了一個非常通用,但功能豐富的高效介面。當您編寫核心代碼時,您將遇到一個或多個這樣的 API,因此它們肯定值得深入研究。
2.6 核心中的計時器和列表【轉】