工作隊列(work queue)是另外一種將工作推後執行的形式,它和tasklet有所不同。工作隊列可以把工作推後,交由一個內核線程去執行,也就是說,這個下半部分可以在進程上下文中執行。這樣,通過工作隊列執行的代碼能占盡進程上下文的所有優勢。最重要的就是工作隊列允許被重新調度甚至是睡眠。
那麼,什麼情況下使用工作隊列,什麼情況下使用tasklet。如果推後執行的任務需要睡眠,那麼就選擇工作隊列。如果推後執行的任務不需要睡眠,那麼就選擇tasklet。另外,如果需要用一個可以重新調度的實體來執行你的下半部處理,也應該使用工作隊列。它是唯一能在進程上下文運行的下半部實現的機制,也只有它才可以睡眠。這意味著在需要獲得大量的內存時、在需要獲取信號量時,在需要執行阻塞式的I/O操作時,它都會非常有用。如果不需要用一個內核線程來推後執行工作,那麼就考慮使用tasklet。
1. 工作、工作隊列和工作者線程
如前所述,我們把推後執行的任務叫做工作(work),描述它的數據結構為work_struct,這些工作以隊列結構組織成工作隊列(workqueue),其數據結構為workqueue_struct,而工作線程就是負責執行工作隊列中的工作。系統默認的工作者線程為events,自己也可以創建自己的工作者線程。
2.表示工作的數據結構
工作用<linux/workqueue.h>中定義的work_struct結構表示:
struct work_struct {
atomic_long_t data;
#define WORK_STRUCT_PENDING 0 /* T if work item pending execution */
#define WORK_STRUCT_FLAG_MASK (3UL)
#define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
struct list_head entry;
work_func_t func;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
這些結構被連接成鏈表。當一個工作者線程被喚醒時,它會執行它的鏈表上的所有工作。工作被執行完畢,它就將相應的work_struct對象從鏈表上移去。當鏈表上不再有對象的時候,它就會繼續休眠。
3.創建推後的工作
要使用工作隊列,首先要做的是創建一些需要推後完成的工作。可以通過DECLARE_WORK在編譯時靜態地建該結構:
DECLARE_WORK(name, void (*func) (void*), void *data);
這樣就會靜態地創建一個名為name,待執行函數為func,參數為data的work_struct結構。
同樣,也可以在運行時通過指針創建一個工作:
INIT_WORK(struct work_struct *work, woid(*func) (void *), void *data);
這會動態地初始化一個由work指向的工作。
4. 工作隊列中待執行的函數
工作隊列待執行的函數原型是:
void work_handler(void*data)
這個函數會由一個工作者線程執行,因此,函數會運行在進程上下文中。默認情況下,允許響應中斷,並且不持有任何鎖。如果需要,函數可以睡眠。需要注意的是,盡管該函數運行在進程上下文中,但它不能訪問用戶空間,因為內核線程在用戶空間沒有相關的內存映射。通常在系統調用發生時,內核會代表用戶空間的進程運行,此時它才能訪問用戶空間,也只有在此時它才會映射用戶空間的內存。
5. 對工作進行調度
現在工作已經被創建,我們可以調度它了。想要把給定工作的待處理函數提交給缺省的events工作線程,只需調用
schedule_work(&work);
work馬上就會被調度,一旦其所在的處理器上的工作者線程被喚醒,它就會被執行。
有時候並不希望工作馬上就被執行,而是希望它經過一段延遲以後再執行。在這種情況下,可以調度它在指定的時間執行:
schedule_delayed_work(&work,delay);
這時,&work指向的work_struct直到delay指定的時鐘節拍用完以後才會執行。