tasklet
tasklet vs 內核定時器
相同:始終在中斷期間運行,始終會在調度他們的同一CPU上運行,而且都接收一個unsigned long參數
不同:不可以要求tasklet在某一給定的時間執行
tasklet對中斷處理例程來說尤其有用。中斷處理例程必須儘可能快的管理硬件中斷,而大部分數據管理則可以安全的延遲到其後的時間。
實際上,與內核定時器類似,tasklet也會在“軟件中斷”上下文以原子模式執行。軟件中斷指打開硬件中斷的同時執行某些異步任務的內核機制。
tasklet以數據結構形式存在,並在使用前必須初始化。調用特定的函數或者使用特定的宏來聲明該結構,即可完成tasklet的初始化:
#include<linux/interrupt.h> struct tasklet_struct{ /*......*/ void(*func)(unsigned long); unsigned long data; }; void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data); #define DECLARE_TASKLET(name, func, data) \
|
tasklet特性:
1.一個tasklet可在稍後被禁止或者重新啓用;只有啓用的次數和禁止的次數相同時,tasklet纔會被執行。
2.和定時器類似,tasklet可以自己註冊自己。
3.tasklet可被調度以在通常的優先級或者高優先級執行。高優先級的tasklet總會優先執行。
4.如果系統負荷不重,則tasklet會立即執行,但始終不會晚於下一個定時器滴答
5.一個tasklet可以和其它tasklet併發,但對自身來講是嚴格串行處理的,也就是說,同一tasklet永遠不會在多個處理器上同時運行:tasklet始終會調度自己在同一CPU上運行;
工作隊列
表面來看,工作隊列類似於tasklet:允許內核代碼請求某個函數在將來的時間被調用。
但其實還是有很多不同:
1.tasklet在軟中斷上下文中運行,因此,所有的tasklet代碼都是原子的。相反,工作隊列函數在一個特殊的內核進程上下文中運行,因此他們有更好的靈活性
尤其是,工作隊列可以休眠!
2.tasklet始終運行在被初始提交的統一處理器上,但這只是工作隊列的默認方式
3.內核代碼可以請求工作隊列函數的執行延遲給定的時間間隔
4.tasklet 執行的很快, 短時期, 並且在原子態, 而工作隊列函數可能是長週期且不需要是原子的,兩個機制有它適合的情形。
兩者的關鍵區別:tasklet會在很短的時間內很快執行,並且以原子模式執行,而工作隊列函數可以具有更長的延遲並且不必原子化。兩種機制有各自適合的情形。
工作隊列有 struct workqueue_struct 類型,在 <linux/workqueue.h> 中定義。一個工作隊列必須明確的在使用前創建,宏爲:
struct workqueue_struct *create_workqueue(const char *name); |
每個工作隊列有一個或多個專用的進程("內核線程"), 這些進程運行提交給這個隊列的函數。 若使用 create_workqueue, 就得到一個工作隊列它在系統的每個處理器上有一個專用的線程。在很多情況下,過多線程對系統性能有影響,如果單個線程就足夠則使用 create_singlethread_workqueue 來創建工作隊列。
提交一個任務給一個工作隊列,在這裏《LDD3》介紹的內核2.6.10和我用的新內核2.6.22.2已經有不同了,老接口已經不能用了,編譯會出錯。這裏我只講2.6.22.2的新接口,至於老的接口我想今後內核不會再有了。從這一點我們可以看出內核發展。
/*需要填充work_struct或delayed_work結構,可以在編譯時完成, 宏如下: */ struct work_struct { |
在將來的某個時間, 這個工作函數將被傳入給定的 data 值來調用。這個函數將在工作線程的上下文運行, 因此它可以睡眠 (你應當知道這個睡眠可能影響提交給同一個工作隊列的其他任務) 工作函數不能訪問用戶空間,因爲它在一個內核線程中運行, 完全沒有對應的用戶空間來訪問。
取消一個掛起的工作隊列入口項可以調用:
int cancel_delayed_work(struct delayed_work *work); |
如果這個入口在它開始執行前被取消,則返回非零。內核保證給定入口的執行不會在調用 cancel_delay_work 後被初始化. 如果 cancel_delay_work 返回 0, 但是, 這個入口可能已經運行在一個不同的處理器, 並且可能仍然在調用 cancel_delayed_work 後在運行. 要絕對確保工作函數沒有在 cancel_delayed_work 返回 0 後在任何地方運行, 你必須跟隨這個調用來調用:
void flush_workqueue(struct workqueue_struct *queue); |
在 flush_workqueue 返回後, 沒有在這個調用前提交的函數在系統中任何地方運行。
而cancel_work_sync會取消相應的work,但是如果這個work已經在運行那麼cancel_work_sync會阻塞,直到work完成並取消相應的work。
當用完一個工作隊列,可以去掉它,使用:
void destroy_workqueue(struct workqueue_struct *queue); |
共享隊列
在許多情況下, 設備驅動不需要它自己的工作隊列。如果你只偶爾提交任務給隊列, 簡單地使用內核提供的共享的默認的隊列可能更有效。若使用共享隊列,就必須明白將和其他人共享它,這意味着不應當長時間獨佔隊列(不能長時間睡眠), 並且可能要更長時間才能獲得處理器。
使用的順序:
(1) 建立 work_struct 或 delayed_work
static struct work_struct jiq_work; |
(2)提交工作
int schedule_work(&jiq_work);/*對於work_struct結構*/ /*返回值的定義和 queue_work 一樣*/ |
若需取消一個已提交給工作隊列入口項, 可以使用 cancel_delayed_work和cancel_work_sync, 但刷新共享隊列需要一個特殊的函數:
void flush_scheduled_work(void); |
因爲不知道誰可能使用這個隊列,因此不可能知道 flush_schduled_work 返回需要多長時間。