W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
無(wú)論何時(shí)你需要調(diào)度一個(gè)動(dòng)作以后發(fā)生, 而不阻塞當(dāng)前進(jìn)程直到到時(shí), 內(nèi)核定時(shí)器是給你的工具. 這些定時(shí)器用來(lái)調(diào)度一個(gè)函數(shù)在將來(lái)一個(gè)特定的時(shí)間執(zhí)行, 基于時(shí)鐘嘀噠, 并且可用作各類任務(wù); 例如, 當(dāng)硬件無(wú)法發(fā)出中斷時(shí), 查詢一個(gè)設(shè)備通過(guò)在定期的間隔內(nèi)檢查它的狀態(tài). 其他的內(nèi)核定時(shí)器的典型應(yīng)用是關(guān)閉軟驅(qū)馬達(dá)或者結(jié)束另一個(gè)長(zhǎng)期終止的操作. 在這種情況下, 延后來(lái)自 close 的返回將強(qiáng)加一個(gè)不必要(并且嚇人的)開(kāi)銷在應(yīng)用程序上. 最后, 內(nèi)核自身使用定時(shí)器在幾個(gè)情況下, 包括實(shí)現(xiàn) schedule_timeout.
一個(gè)內(nèi)核定時(shí)器是一個(gè)數(shù)據(jù)結(jié)構(gòu), 它指導(dǎo)內(nèi)核執(zhí)行一個(gè)用戶定義的函數(shù)使用一個(gè)用戶定義的參數(shù)在一個(gè)用戶定義的時(shí)間. 這個(gè)實(shí)現(xiàn)位于 <linux/timer.h> 和 kernel/timer.c 并且在"內(nèi)核定時(shí)器"一節(jié)中詳細(xì)介紹.
被調(diào)度運(yùn)行的函數(shù)幾乎確定不會(huì)在注冊(cè)它們的進(jìn)程在運(yùn)行時(shí)運(yùn)行. 它們是, 相反, 異步運(yùn)行. 直到現(xiàn)在, 我們?cè)谖覀兊睦域?qū)動(dòng)中已經(jīng)做的任何事情已經(jīng)在執(zhí)行系統(tǒng)調(diào)用的進(jìn)程上下文中運(yùn)行. 當(dāng)一個(gè)定時(shí)器運(yùn)行時(shí), 但是, 這個(gè)調(diào)度進(jìn)程可能睡眠, 可能在不同的一個(gè)處理器上運(yùn)行, 或者很可能已經(jīng)一起退出.
這個(gè)異步執(zhí)行類似當(dāng)發(fā)生一個(gè)硬件中斷時(shí)所發(fā)生的( 這在第 10 章詳細(xì)討論 ). 實(shí)際上, 內(nèi)核定時(shí)器被作為一個(gè)"軟件中斷"的結(jié)果而實(shí)現(xiàn). 當(dāng)在這種原子上下文運(yùn)行時(shí), 你的代碼易受到多個(gè)限制. 定時(shí)器函數(shù)必須是原子的以所有的我們?cè)诘?1 章"自旋鎖和原子上下文"一節(jié)中曾討論過(guò)的方式, 但是有幾個(gè)附加的問(wèn)題由于缺少一個(gè)進(jìn)程上下文而引起的. 我們將介紹這些限制; 在后續(xù)章節(jié)的幾個(gè)地方將再次看到它們. 循環(huán)被調(diào)用因?yàn)樵由舷挛牡囊?guī)則必須認(rèn)真遵守, 否則系統(tǒng)會(huì)發(fā)現(xiàn)自己陷入大麻煩中.
為能夠被執(zhí)行, 多個(gè)動(dòng)作需要進(jìn)程上下文. 當(dāng)你在進(jìn)程上下文之外(即, 在中斷上下文), 你必須遵守下列規(guī)則:
沒(méi)有允許存取用戶空間. 因?yàn)闆](méi)有進(jìn)程上下文, 沒(méi)有和任何特定進(jìn)程相關(guān)聯(lián)的到用戶空間的途徑.
這個(gè) current 指針在原子態(tài)沒(méi)有意義, 并且不能使用因?yàn)橄嚓P(guān)的代碼沒(méi)有和已被中斷的進(jìn)程的聯(lián)系.
不能進(jìn)行睡眠或者調(diào)度. 原子代碼不能調(diào)用 schedule 或者某種 wait_event, 也不能調(diào)用任何其他可能睡眠的函數(shù). 例如, 調(diào)用 kmalloc(..., GFP_KERNEL) 是違犯規(guī)則的. 旗標(biāo)也必須不能使用因?yàn)樗鼈兛赡芩?
內(nèi)核代碼能夠告知是否它在中斷上下文中運(yùn)行, 通過(guò)調(diào)用函數(shù) in_interrupt(), 它不要參數(shù)并且如果處理器當(dāng)前在中斷上下文運(yùn)行就返回非零, 要么硬件中斷要么軟件中斷.
一個(gè)和 in_interrupt() 相關(guān)的函數(shù)是 in_atomic(). 它的返回值是非零無(wú)論何時(shí)調(diào)度被禁止; 這包含硬件和軟件中斷上下文以及任何持有自旋鎖的時(shí)候. 在后一種情況, current 可能是有效的, 但是存取用戶空間被禁止, 因?yàn)樗軐?dǎo)致調(diào)度發(fā)生. 無(wú)論何時(shí)你使用 in_interrupt(), 你應(yīng)當(dāng)真正考慮是否 in_atomic 是你實(shí)際想要的. 2 個(gè)函數(shù)都在 <asm/hardirq.h> 中聲明.
內(nèi)核定時(shí)器的另一個(gè)重要特性是一個(gè)任務(wù)可以注冊(cè)它本身在后面時(shí)間重新運(yùn)行. 這是可能的, 因?yàn)槊總€(gè) timer_list 結(jié)構(gòu)在運(yùn)行前從激活的定時(shí)器鏈表中去連接, 并且因此能夠馬上在其他地方被重新連接. 盡管反復(fù)重新調(diào)度相同的任務(wù)可能表現(xiàn)為一個(gè)無(wú)意義的操作, 有時(shí)它是有用的. 例如, 它可用作實(shí)現(xiàn)對(duì)設(shè)備的查詢.
也值得了解在一個(gè) SMP 系統(tǒng), 定時(shí)器函數(shù)被注冊(cè)時(shí)相同的 CPU 來(lái)執(zhí)行, 為在任何可能的時(shí)候獲得更好的緩存局部特性. 因此, 一個(gè)重新注冊(cè)它自己的定時(shí)器一直運(yùn)行在同一個(gè) CPU.
不應(yīng)當(dāng)被忘記的定時(shí)器的一個(gè)重要特性是, 它們是一個(gè)潛在的競(jìng)爭(zhēng)條件的源, 即便在一個(gè)單處理器系統(tǒng). 這是它們與其他代碼異步運(yùn)行的一個(gè)直接結(jié)果. 因此, 任何被定時(shí)器函數(shù)存取的數(shù)據(jù)結(jié)構(gòu)應(yīng)當(dāng)保護(hù)避免并發(fā)存取, 要么通過(guò)原子類型( 在第 1 章的"原子變量"一節(jié)) 要么使用自旋鎖( 在第 9 章討論 ).
內(nèi)核提供給驅(qū)動(dòng)許多函數(shù)來(lái)聲明, 注冊(cè), 以及去除內(nèi)核定時(shí)器. 下列的引用展示了基本的代碼塊:
#include <linux/timer.h>
struct timer_list
{
/* ... */
unsigned long expires;
void (*function)(unsigned long);
unsigned long data;
};
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);
這個(gè)數(shù)據(jù)結(jié)構(gòu)包含比曾展示過(guò)的更多的字段, 但是這 3 個(gè)是打算從定時(shí)器代碼自身以外被存取的. 這個(gè) expires 字段表示定時(shí)器期望運(yùn)行的 jiffies 值; 在那個(gè)時(shí)間, 這個(gè) function 函數(shù)被調(diào)用使用 data 作為一個(gè)參數(shù). 如果你需要在參數(shù)中傳遞多項(xiàng), 你可以捆綁它們作為一個(gè)單個(gè)數(shù)據(jù)結(jié)構(gòu)并且傳遞一個(gè)轉(zhuǎn)換為 unsiged long 的指針, 在所有支持的體系上的一個(gè)安全做法并且在內(nèi)存管理中相當(dāng)普遍( 如同 15 章中討論的 ). expires 值不是一個(gè) jiffies_64 項(xiàng)因?yàn)槎〞r(shí)器不被期望在將來(lái)很久到時(shí), 并且 64-位操作在 32-位平臺(tái)上慢.
這個(gè)結(jié)構(gòu)必須在使用前初始化. 這個(gè)步驟保證所有的成員被正確建立, 包括那些對(duì)調(diào)用者不透明的. 初始化可以通過(guò)調(diào)用 init_timer 或者 安排 TIMER_INITIALIZER 給一個(gè)靜態(tài)結(jié)構(gòu), 根據(jù)你的需要. 在初始化后, 你可以改變 3 個(gè)公共成員在調(diào)用 add_timer 前. 為在到時(shí)前禁止一個(gè)已注冊(cè)的定時(shí)器, 調(diào)用 del_timer.
jit 模塊包括一個(gè)例子文件, /proc/jitimer ( 為 "just in timer"), 它返回一個(gè)頭文件行以及 6 個(gè)數(shù)據(jù)行. 這些數(shù)據(jù)行表示當(dāng)前代碼運(yùn)行的環(huán)境; 第一個(gè)由讀文件操作產(chǎn)生并且其他的由定時(shí)器. 下列的輸出在編譯內(nèi)核時(shí)被記錄:
phon% cat /proc/jitimer
time delta inirq pid cpu command
33565837 0 0 1269 0 cat
33565847 10 1 1271 0 sh
33565857 10 1 1273 0 cpp0
33565867 10 1 1273 0 cpp0
33565877 10 1 1274 0 cc1
33565887 10 1 1274 0 cc1
在這個(gè)輸出, time 字段是代碼運(yùn)行時(shí)的 jiffies 值, delta 是自前一行的 jiffies 改變值, inirq 是由 in_interrupt 返回的布爾值, pid 和 command 指的是當(dāng)前進(jìn)程, 以及 cpu 是在使用的 CPU 的數(shù)目( 在單處理器系統(tǒng)上一直為 0).
如果你讀 /proc/jitimer 當(dāng)系統(tǒng)無(wú)負(fù)載時(shí), 你會(huì)發(fā)現(xiàn)定時(shí)器的上下文是進(jìn)程 0, 空閑任務(wù), 它被稱為"對(duì)換進(jìn)程"只要由于歷史原因.
用來(lái)產(chǎn)生 /proc/jitimer 數(shù)據(jù)的定時(shí)器是缺省每 10 jiffies 運(yùn)行一次, 但是你可以在加載模塊時(shí)改變這個(gè)值通過(guò)設(shè)置 tdelay ( timer delay ) 參數(shù).
下面的代碼引用展示了 jit 關(guān)于 jitimer 定時(shí)器的部分. 當(dāng)一個(gè)進(jìn)程試圖讀取我們的文件, 我們建立這個(gè)定時(shí)器如下:
unsigned long j = jiffies;
/* fill the data for our timer function */
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; /* parameter */
add_timer(&data->timer);
/* wait for the buffer to fill */
wait_event_interruptible(data->wait, !data->loops);
The actual timer function looks like this:
void jit_timer_fn(unsigned long arg)
{
struct jit_data *data = (struct jit_data *)arg;
unsigned long j = jiffies;
data->buf += sprintf(data->buf, "%9li %3li %i %6i %i %s\n",
j, j - data->prevjiffies, in_interrupt() ? 1 : 0,
current->pid, smp_processor_id(), current->comm);
if (--data->loops) {
data->timer.expires += tdelay;
data->prevjiffies = j;
add_timer(&data->timer);
} else {
wake_up_interruptible(&data->wait);
}
}
定時(shí)器 API 包括幾個(gè)比上面介紹的那些更多的功能. 下面的集合是完整的核提供的函數(shù)列表:
int mod_timer(struct timer_list *timer, unsigned long expires);
更新一個(gè)定時(shí)器的超時(shí)時(shí)間, 使用一個(gè)超時(shí)定時(shí)器的一個(gè)普通的任務(wù)(再一次, 關(guān)馬達(dá)軟驅(qū)定時(shí)器是一個(gè)典型例子). mod_timer 也可被調(diào)用于非激活定時(shí)器, 那里你正常地使用 add_timer.
int del_timer_sync(struct timer_list *timer);
如同 del_timer 一樣工作, 但是還保證當(dāng)它返回時(shí), 定時(shí)器函數(shù)不在任何 CPU 上運(yùn)行. del_timer_sync 用來(lái)避免競(jìng)爭(zhēng)情況在 SMP 系統(tǒng)上, 并且在 UP 內(nèi)核中和 del_timer 相同. 這個(gè)函數(shù)應(yīng)當(dāng)在大部分情況下比 del_timer 更首先使用. 這個(gè)函數(shù)可能睡眠如果它被從非原子上下文調(diào)用, 但是在其他情況下會(huì)忙等待. 要十分小心調(diào)用 del_timer_sync 當(dāng)持有鎖時(shí); 如果這個(gè)定時(shí)器函數(shù)試圖獲得同一個(gè)鎖, 系統(tǒng)會(huì)死鎖. 如果定時(shí)器函數(shù)重新注冊(cè)自己, 調(diào)用者必須首先確保這個(gè)重新注冊(cè)不會(huì)發(fā)生; 這常常同設(shè)置一個(gè)" 關(guān)閉 "標(biāo)志來(lái)實(shí)現(xiàn), 這個(gè)標(biāo)志被定時(shí)器函數(shù)檢查.
int timer_pending(const struct timer_list * timer);
返回真或假來(lái)指示是否定時(shí)器當(dāng)前被調(diào)度來(lái)運(yùn)行, 通過(guò)調(diào)用結(jié)構(gòu)的其中一個(gè)不透明的成員.
為了使用它們, 盡管你不會(huì)需要知道內(nèi)核定時(shí)器如何實(shí)現(xiàn), 這個(gè)實(shí)現(xiàn)是有趣的, 并且值得看一下它們的內(nèi)部.
定時(shí)器的實(shí)現(xiàn)被設(shè)計(jì)來(lái)符合下列要求和假設(shè):
定時(shí)器管理必須盡可能簡(jiǎn)化.
設(shè)計(jì)應(yīng)當(dāng)隨著激活的定時(shí)器數(shù)目上升而很好地適應(yīng).
大部分定時(shí)器在幾秒或最多幾分鐘內(nèi)到時(shí), 而帶有長(zhǎng)延時(shí)的定時(shí)器是相當(dāng)少見(jiàn).
一個(gè)定時(shí)器應(yīng)當(dāng)在注冊(cè)它的同一個(gè) CPU 上運(yùn)行.
由內(nèi)核開(kāi)發(fā)者想出的解決方法是基于一個(gè)每-CPU 數(shù)據(jù)結(jié)構(gòu). 這個(gè) timer_list 結(jié)構(gòu)包括一個(gè)指針指向這個(gè)的數(shù)據(jù)結(jié)構(gòu)在它的 base 成員. 如果 base 是 NULL, 這個(gè)定時(shí)器沒(méi)有被調(diào)用運(yùn)行; 否則, 這個(gè)指針告知哪個(gè)數(shù)據(jù)結(jié)構(gòu)(并且, 因此, 哪個(gè) CPU )運(yùn)行它. 每-CPU 數(shù)據(jù)項(xiàng)在第 8 章的"每-CPU變量"一節(jié)中描述.
無(wú)論何時(shí)內(nèi)核代碼注冊(cè)一個(gè)定時(shí)器( 通過(guò) add_timer 或者 mod_timer), 操作最終由 internal_add_timer 進(jìn)行( 在kernel/timer.c), 它依次添加新定時(shí)器到一個(gè)雙向定時(shí)器鏈表在一個(gè)關(guān)聯(lián)到當(dāng)前 CPU 的"層疊表" 中.
這個(gè)層疊表象這樣工作: 如果定時(shí)器在下一個(gè) 0 到 255 jiffies 內(nèi)到時(shí), 它被添加到專供短時(shí)定時(shí)器 256 列表中的一個(gè)上, 使用 expires 成員的最低有效位. 如果它在將來(lái)更久時(shí)間到時(shí)( 但是在 16,384 jiffies 之前 ), 它被添加到基于 expires 成員的 9 - 14 位的 64 個(gè)列表中一個(gè). 對(duì)于更長(zhǎng)的定時(shí)器, 同樣的技巧用在 15 - 20 位, 21 - 26 位, 和 27 - 31 位. 帶有一個(gè)指向?qū)?lái)還長(zhǎng)時(shí)間的 expires 成員的定時(shí)器( 一些只可能發(fā)生在 64-位 平臺(tái)上的事情 ) 被使用一個(gè)延時(shí)值 0xffffffff 進(jìn)行哈希處理, 并且?guī)в性谶^(guò)去到時(shí)的定時(shí)器被調(diào)度來(lái)在下一個(gè)時(shí)鐘嘀噠運(yùn)行. (一個(gè)已經(jīng)到時(shí)的定時(shí)器模擬有時(shí)在高負(fù)載情況下被注冊(cè), 特別的是如果你運(yùn)行一個(gè)可搶占內(nèi)核).
當(dāng)觸發(fā) __run_timers, 它為當(dāng)前定時(shí)器嘀噠執(zhí)行所有掛起的定時(shí)器. 如果 jiffies 當(dāng)前是 256 的倍數(shù), 這個(gè)函數(shù)還重新哈希處理一個(gè)下一級(jí)別的定時(shí)器列表到 256 短期列表, 可能地層疊一個(gè)或多個(gè)別的級(jí)別, 根據(jù)jiffies 的位表示.
這個(gè)方法, 雖然第一眼看去相當(dāng)復(fù)雜, 在幾個(gè)和大量定時(shí)器的時(shí)候都工作得很好. 用來(lái)管理每個(gè)激活定時(shí)器的時(shí)間獨(dú)立于已經(jīng)注冊(cè)的定時(shí)器數(shù)目并且限制在幾個(gè)對(duì)于它的 expires 成員的二進(jìn)制表示的邏輯操作上. 關(guān)聯(lián)到這個(gè)實(shí)現(xiàn)的唯一的開(kāi)銷是給 512 鏈表頭的內(nèi)存( 256 短期鏈表和 4 組 64 更長(zhǎng)時(shí)間的列表) -- 即 4 KB 的容量.
函數(shù) __run_timers, 如同 /proc/jitimer 所示, 在原子上下文運(yùn)行. 除了我們已經(jīng)描述過(guò)的限制, 這個(gè)帶來(lái)一個(gè)有趣的特性: 定時(shí)器剛好在合適的時(shí)間到時(shí), 甚至你沒(méi)有運(yùn)行一個(gè)可搶占內(nèi)核, 并且 CPU 在內(nèi)核空間忙. 你可以見(jiàn)到發(fā)生了什么當(dāng)你在后臺(tái)讀 /proc/jitbusy 時(shí)以及在前臺(tái) /proc/jitimer. 盡管系統(tǒng)看來(lái)牢固地被鎖住被這個(gè)忙等待系統(tǒng)調(diào)用, 內(nèi)核定時(shí)器照樣工作地不錯(cuò).
但是, 記住, 一個(gè)內(nèi)核定時(shí)器還遠(yuǎn)未完善, 因?yàn)樗芾塾?jitter 和 其他由硬件中斷引起怪物, 還有其他定時(shí)器和其他異步任務(wù). 雖然一個(gè)關(guān)聯(lián)到簡(jiǎn)單數(shù)字 I/O 的定時(shí)器對(duì)于一個(gè)如同運(yùn)行一個(gè)步進(jìn)馬達(dá)或者其他業(yè)余電子設(shè)備等簡(jiǎn)單任務(wù)是足夠的, 它常常是不合適在工業(yè)環(huán)境中的生產(chǎn)系統(tǒng). 對(duì)于這樣的任務(wù), 你將最可能需要依賴一個(gè)實(shí)時(shí)內(nèi)核擴(kuò)展.
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: