核心定時器

2021-06-22 03:25:14 字數 4872 閱讀 3905

核心定時器

分類: linux裝置驅動程式第三版

2013-01-21 20:57

475人閱讀收藏

舉報 核心定時器

如果我們需要在將來的某個時間點除錯執行某個動作,同時在該時間點到達之前不會阻塞當前里程,則可以使用核心定時器。核心定時器可以在未來的某個時間點(基於時間滴答)排程執行某個函式。如硬體無法產和中斷,則可以週期性的輪詢裝置狀態。

乙個核心定時器是乙個資料結構struct timer_list,它告訴核心在使用者定義的時間點使用使用者定義的引數來執行乙個使用者定義的函式。其實現位於和kernel/timer.c檔案。我們將在核心定時器的實現一節中對此進行詳細描述。

被排程執行的定時器中的函式幾乎肯定不會在註冊這些函式的程序正在執行的時候執行。相反這些函式會非同步的執行。目前為止,我們提供的示例驅動程式**都在程序執行系統呼叫的上下文中執行。但是,當定時器執行時,註冊該定時器的程序可能正在休眠或在其他處理器上執行,或者乾脆已經退出。

這種非同步執行類似於硬體中斷發生的情景。實際上,核心定時器常常是作為「軟體中斷」的結果而執行的。在這種原子性的上下文中執行時,**會受到許多限制。定時器函式必須以我們在我們在第五章「自旋鎖和原子上下文」一節中討論的方式原子地執行,中間不能休眠,這種非程序上下文還會帶來其他一些問題。現在我們就來討論這些限制。

許多動作需要在程序上下文中才能執行。如果處於程序上下文之外(比如在中斷上下文中),則必須遵守如下規則

核心**可以通過in_interrupt()來判斷自己是否正執行於中斷上下文,該函式無引數,如果是返回非零,無論是硬體中斷還是軟體中斷。

函式in_atomic()可以判斷排程是否被禁止,如果禁止函式返回非零。在硬體和軟體中斷上下文以及擁有自旋鎖的任何時間點都不允許排程。在擁有自旋鎖時current是可用的,但禁止訪問使用者空間,因為這會導致排程的發生。

不管何時使用in_interrupt()都應該考慮是否真正應該使用的是in_atomic()。這兩個函式均在中宣告。

核心定時器的另乙個重要特性是,任務(定時任務)可以將自己註冊以在稍後的時間重新執行。這種可能性是因為每個timer_list結構都會在執行之前從活動定時器鍊錶中移走,這就可以立即鏈入其它的鍊錶。儘管多次排程同一任務似乎是一你件沒多大意義的操作,但有時還是很有用的。如在輪詢裝置時可以使用,以及在跑i/o的過程中查詢速率。

另乙個值得了解的是,在smp系統中,定時器函式會由註冊它的同一cpu執行,這樣可以盡可能獲得快取的局域性(locality)。因此,乙個註冊自己的定時器始終會在同一cpu上執行。

關於定時器,還有一點要謹記的是:即使在單處理器系統上,定時器也會是競態的潛在**。這是由其它非同步執行(有可能是軟體中斷還可能被硬體中斷打斷)的特點直接導致的。因此,任何通過定時器函式訪問的資料結構都應該針對併發訪問進行保護。可以使用原子型別或自自旋鎖

定時器api

核心為驅動程式提供了一組用來宣告、註冊和刪除核心定時器的函式。

void init_timer(struct timer_list *timer);

struct timer_list timer_initializer(_function, _expires, _data);

void add_timer(struct timer_list *timer);

int del_timer(struct timer_list *timer);

timer_list在使用前必須初始化,在初始化之後,可在呼叫add_timer之前修改那三個公共的字段(expires,fun,data),如果要在定時器到期前禁止乙個已註冊的定時器,則可呼叫del_timer函式。下面是定時器示例**:

[cpp]view plain

copy

unsigned long j = jiffies;  

/*為定時器函式填充資料*/

data->prevjiffies = j;  

data->buf = buf2;  

data->loops = jit_async_loops;  

/* register the timer */

data->timer.data = (unsigned long)data;  

data->timer.function = jit_timer_fn;  

data->timer.expires = j + tdelay;  

add_timer(&data->timer);  

/*等待緩衝區以填充資料*/

wait_event_interruptible(data->wait, !data->loops);  

實際的定時器函式如下:

[cpp]view plain

copy

void jit_timer_fn(unsigned long arg)  

else  

}  

除了上面的幾個函式介面外,核心定時器api還包含其他幾個函式,下面給出這些函式的完整描述:

int mod_timer(struct timer_list *timer, unsigned long expires);

更新某個定時器到期時間,經常用於超時定時器。我們也可以在通常使用add_timer的時候在不活動的定時器上呼叫mod_timer。

int del_timer_sync(struct timer_list *timer)

和del_timer的工作類似,但該函式可確保在返回時沒有任何cup在執行定時器函式。它可用於smp系統上避免競態,這和單處理器核心中的del_timer是一樣的。在大多數情況下,應該優先呼叫這個函式而不是del_timer函式。在非原子上下文中呼叫,該函式可能會引起休眠,但在其他情況下呼叫會進入忙等待。在擁有鎖時,應該格外小心呼叫del_timer_sync,因為如果定時器函式企圖獲取相同的鎖,系統就會進入死鎖。

int timer_pending(const struct timer_list *timer);

該函式通過讀取timer_list結構的乙個不可見欄位來返回定時器是否正在被排程執行。

核心定時器的實現

外部機制:當產生時鐘中斷後,在時鐘中斷處例程timer_interrupt()中上半部分會呼叫update_process_times()函式,該函式更新程序的時間片以及修改修改的程序的動態優先順序,如果有必要將會告訴高度器重新除錯,它還會呼叫run_local_timers(),在這個函式中標記timer_softirq,表明有timer需要執行,以便核心在適當的時候去執行定時器,這樣softirq機制在適當的時候就會執行定時器佇列

內部機制:

linux實現定時器的演算法比較精巧,我們不妨先來看看幾個簡單的演算法:

乙個最直接的方法就是將所有的定時器按照時間從小到大的順序排列起來,這樣每次時鐘中斷下半部分處理例程只要檢查當前節點是不是到期就可以了。如果沒有到期,那麼在下次時鐘中斷再判斷,如果到期了,就執行規定的操作,然後將當前節點的指標往後移乙個。在實現上這種方法很簡單,也不需要要多餘的空間。但是如果鍊錶很長,第次插入的時候排序就要花比較多的時間。

對於上面的方法,我們可以採用hash表的方式來改進,即採用平均分段的方式來組織鍊錶。比如,我們將到期的jiffies數按照0-99,100-199,200-299分段,每乙個定時器到自己所屬的時段中進行排序。但是當定時器數量太大的時候,這個方法和上面的那個方法面臨同樣的問題,那就是在插入的時候花在查詢上的時間太大。

如果我們採用不平均的方法來分段,那麼情況就大為不同了。如0-3,4-7,8-15,16-31,……。區間長度呈指數上公升,這樣,就不會有太多的分段。而且當前要處理的定時器在比較短的鍊錶中,排序和搜尋速度都可以大大加快。因為我們關心的都是當前時刻要處理的定時器,而對離執行時間還有很長的定時器是不是需要關心的,所以時間距離太遠的定時器我們只要將它連到鍊錶中就可以了,用不著排序。linux核心就是採用了上面平均分段的hash表思想。

下面是定時器最基本的乙個資料結構:

struct timer_list

不管何時核心**註冊了乙個定時器(通過add_timer或者mod_timer),其操作最終會由internal_add_timer(kernel/timer.c中定義)執行,該函式又會將新的定時器新增到當前cpu關聯的「級聯表」中的定時器又向鍊錶中。

關於「級聯表」的工作方式簡單說一下,核心通過 下面結構管理每個cpu上的定時器:

struct tvec_base

通過以上結構就能表達在接下來2^32個jiffies到期的定時器。

__run_timers被激發時,它會執行當前定時器上的所有掛起的定時器。如果當前jiffies是256的倍數,該函式還會將一下級定時器鍊錶重新雜湊到256個短期鍊錶中,同時還能根據jiffies的位對其他級別的定時器做級聯處理。

這種方法看起來有些複雜,但能很好處理定時器不多或有大量定時器的情況。用來管理每個活動定時器所需的必要時間和已註冊的定時器數量無關,這個時間被限定於定時器expires欄位二進位制表達上的幾個邏輯操作。這種實現唯一的開銷在於512個煉表頭(256個短期鍊錶和4組64個的長期鍊錶)占用了4kb(512*2*4)的儲存空間。

__run_timers執行在原子上下文中,即使我們執行的不是搶占式的核心,定時器也會在正確的時間到期。

需要謹記的是,核心定時器離完美還有很大的距離,因為它受到jitter以及由硬體中斷、其他定時器和非同步任務所產生的影響。和簡單數字i/o關聯的定時器對簡單任務來說足夠了,比如控制步進電機或者業餘電子裝置,但通常不適合於工業環境下的生產系統。對這類任務,我們需要借助某種實時的核心擴充套件。

核心定時器

linux核心2.4版中去掉了老版本核心中的靜態定時器機制,而只留下動態定時器。相應地在timer bh 函式中也不再通過 run old timers 函式來執行老式的靜態定時器。動態定時器與靜態定時器這二個概念是相對於linux核心定時器機制的可擴充套件 功能而言的,動態定時器是指核心的定時器佇...

核心定時器

核心中最終的計時資源是定時器。定時器用於定時器超時處理程式在未來某個特定時間點執行,或者週期性的輪詢硬體的狀態。linux提供了核心定時器完成這類工作。定 時器的只需要執行一些初始化的操作,如 設定乙個超時時間,指定超時要呼叫的函式,然後啟用定時器就可以了。它的處理和工作佇列還是有點類似的。和任務隊...

核心定時器,

ldd3 當定時器執行時 排程該定時器的程序可能正在睡眠,或在其它處理器上執行,或乾脆推出。沒有執行定時器,排程它的程式推出了,定時器該如何?2.6.35。22核心 gcc 4.4.5 include include include include include include include i...