tasklet、工作隊列和共享隊列

tasklet(小任務):

tasklet在很多方面類似內核定時器:他們始終在中斷期間運行,始終會在調度他們的同一CPU上運行,而且都接收一個unsigned long參數。不同的是,不能要求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);

DECLARE_TASKLET(name, func, data);

DECLARE_TASKLET_DISABLED(name, func, data);

tasklet特性:

一個tasklet可以稍後禁止或者重新啓用,只有啓用和禁止的次數相同時,tasklet纔會被執行

和定時器類似,tasklet也可以註冊自己

tasklet可被調度以在通常的優先級或者高優先級執行。高優先級的tasklet總會首先執行

如果系統負荷不重,則tasklet會立即得到執行,但始終不會晚於下一個定時器滴答

一個tasklet可以和其他tasklet併發,但對自身來講是嚴格串行處理的,也就是同一個tasklet永遠不會在多個處理器上同時運行


tasklet相關的內核接口:

void tasklet_disable(struct tasklet_struct *t);

禁止指定的tasklet,該tasklet仍然可以用tasklet_schedule調度,但其執行被推遲,直到該tasklet被重新啓用。如果tasklet當前正在運行,該函數會進入忙等待直到tasklet退出爲止。因此,在調用tasklet_disable之後,我們可以確信該tasklet不會在系統中任何地方運行。


void tasklet_disable_nosync(struct tasklet_struct *t);

禁用指定的tasklet,但不會等待任何運行的tasklet推出。

void tasklet_enable(struct tasklet_struct *t);

啓用一個先前被禁止的tasklet,如果tasklet已經被調度,那麼會很快運行,對tasklet_enable的調用必須和每個對tasklet_disable的調用匹配,因爲內核對每個tasklet保存有一個計數器。


void tasklet_schedule(struct tasklet_struct *t);

調度執行指定的tasklet。如果在獲得運行機會之前,某個tasklet被再次調度,則該tasklet只會運行一次。但是如果在該tasklet運行時被調度,就會在完成後再次運行。這樣,可確保正在處理時間時發生的其他事件也會被接收並注意到。這種行爲也允許tasklet重新調度自身。


void tasklet_hi_schedule(struct tasklet_struct *t);

調度指定的tasklet以高優先級執行,當前軟件中斷處理例程運行時,它會在處理其它軟件中斷任務(包括通常的tasklet)之前處理高優先級的tasklet。理想狀態下只有具備低延遲需求的任務才能使用這個函數,這樣可以避免由其他軟件中斷處理例程引入的額外延遲。


void tasklet_kill(struct tasklet_struct *t);

該函數確保指定的tasklet不會被再次調度。如果tasklet正被調度執行,該函數會等待其退出,如果tasklet重新調度自身,則應該避免在調用tasklet_kill之前完成重新調度,這個del_timer_sync的處理類似。


tasklet的實現在kernel/softirq.c中,其中有兩個tasklet鏈表,他們作爲per-CPU數據結構而聲明。



工作隊列(warkqueue):

表面上和tasklet沒什麼區別,但兩者之間存在重要區別:

tasklet在軟件中斷上下文中運行,所有tasklet代碼都必須是原子的。工作隊列在特殊的內核進程上下文,可以休眠。

tasklet始終運行在被初始提交的同一處理器上,但這只是工作隊列的默認方式。

內核代碼可以請求工作隊列函數的執行延遲給定的時間間隔。

區別關鍵在於,tasklet會在很短時間內很快執行,並且以原子模式執行,而工作隊列函數可具有更長的延遲且不必原子化。


工作隊列有struct workqueue_struct 類型,該結構定義在<linux/workqueue.h>中,在使用之前 ,必須顯式創建一個工作隊列,可使用下面兩個函數之一:

struct workqueue_struct *create_workqueue(const char *name);

struct workqueue_struct *create_singlethread_workqueue(const char *name);

每個工作隊列有一個或者多個專用的進程(“內核線程”),這些進程運行提交到該隊列的函數,create_workqueue會給在系統中的每個處理器上爲工作隊列創建專用的線程。如果單個線程夠用,應使用create_singlethread_workqueue。


向一個工作隊列提交一個任務,需要填充一個work_struct結構,編譯時構造可以使用宏:

DECLARE_WORK(name, void (*function) (void *), void *data):

其中name是結構名稱

運行時構造:
INIT_WORK(struct work_struct *work, void (*function)(void *), void *data);

PREPARE_WORK(struct work_struct *work, void (*function)(void *), void *data);

INIT_WORK完成更加徹底的結構初始化工作;首次構造該結構時,應該使用這個宏。

PREPARE_WORK完成幾乎相同的工作,但不會初始化用來將work_struct結構鏈接到工作隊列的指針。如果結構已經被提交到工作隊列,而只需修改該結構,則應該使用PREPARE_WORK而不是INIT_WORK。


提交到工作隊列,使用下兩函數之一:
int queue_work(struct workqueue_struct *queue, struct work_struct *work);

int queue_delayed_work(struct workqueue_struct *queue, struct work_strcut *work, unsigned long delay);

delayed版本會在至少經過指定的jiffies之後才執行,成功添加返回0,如果非0表示已經在該隊列不能兩次添加。

工作函數在工作線程上下文運行,必要時可以休眠,應該仔細考慮休眠會不會影響提交到同一工作隊列的其他任務。該函數不能訪問用戶空間,因爲是在內核線程,沒有用戶空間可以訪問。

取消掛起的任務隊列入口項可調用:

int cancel_delayed_work(struct work_queue *work);

如果在執行前被取消則返回非零值,返回0表示已經在其他處理器上運行。應此在cancel_delayed_work返回後,工作函數仍可能在運行。爲了絕對確保在cancel_delayed_work返回0後,工作函數不會在系統任何地方運行,則應該隨後調用下面的函數:

void flush_workqueue(struct workqueue_struct *queue);

在flush_workqueue返回後,任何在該調用之前被提交的工作函數都不會在系統任何地方運行。


在結束對工作隊列的使用後,可調用下面的函數釋放相關資源:

void destroy_workqueue(struct workqueue *queue);



共享隊列:

可以使用內核提供的共享的默認工作隊列。共享意味着不應長時間獨佔該隊列,即不能長時間休眠,而我們的人物可能需要更長的時間才能的到執行。



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