轉自:http://blog.csdn.net/bullbat/article/details/7410563
版權聲明:本文為博主原創文章,未經博主允許不得轉載。
工作隊列(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指定的時鍾節拍用完以后才會執行。
上面內容部分摘自:http://blog.csdn.net/zyhorse2010/article/details/6455026
6. 工作隊列的簡單應用
在Workqueue機制中,提供了一個系統默認的workqueue隊列——keventd_wq,這個隊列是Linux系統在初始化的時候就創建的。用戶可以直接初始化一個work_struct對象,然后在該隊列中進行調度,使用更加方便。
當用戶調用workqueue的初始化接口create_workqueue或者create_singlethread_workqueue對workqueue隊列進行初始化時,內核就開始為用戶分配一個workqueue對象,並且將其鏈到一個全局的workqueue隊列中。然后Linux根據當前CPU的情況,為workqueue對象分配與CPU個數相同的cpu_workqueue_struct對象,每個cpu_workqueue_struct對象都會存在一條任務隊列。緊接着,Linux為每個cpu_workqueue_struct對象分配一個內核thread,即內核daemon去處理每個隊列中的任務。至此,用戶調用初始化接口將workqueue初始化完畢,返回workqueue的指針。
在初始化workqueue過程中,內核需要初始化內核線程,注冊的內核線程工作比較簡單,就是不斷的掃描對應cpu_workqueue_struct中的任務隊列,從中獲取一個有效任務,然后執行該任務。所以如果任務隊列為空,那么內核daemon就在cpu_workqueue_struct中的等待隊列上睡眠,直到有人喚醒daemon去處理任務隊列。
Workqueue初始化完畢之后,將任務運行的上下文環境構建起來了,但是具體還沒有可執行的任務,所以,需要定義具體的work_struct對象。然后將work_struct加入到任務隊列中,Linux會喚醒daemon去處理任務。
上面內容摘自:http://hi.baidu.com/%CD%EA%C3%C0%CB%C4%C4%EA/blog/item/412b8833ca91b2e61b4cff5b.html
7.補充與實驗
對於內核現成的隊列,我們初始化完work后直接用queue_schedule加入系統默認的workqueue隊列——keventd_wq並調度執行,對於我們從新創建的工作隊列,需要用create_queue來創建work_queue,然后初始化work,最后,還需要使用queue_work加入我們創建的工作隊列並調度執行。
下面是一個兩種方案的使用例子:
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
MODULE_AUTHOR("Mike Feng");
/*測試數據結構*/
struct my_data
{
structwork_struct my_work;
intvalue;
};
struct workqueue_struct *wq=NULL;
struct work_struct work_queue;
/*初始化我們的測試數據*/
struct my_data* init_data(structmy_data *md)
{
md=(structmy_data*)kmalloc(sizeof(struct my_data),GFP_KERNEL);
md->value=1;
md->my_work=work_queue;
returnmd;
}
/*工作隊列函數*/
static void work_func(struct work_struct *work)
{
structmy_data *md=container_of(work,structmy_data,my_work);
printk("<2>""Thevalue of my data is:%d\n",md->value);
}
static __init intwork_init(void)
{
structmy_data *md=NULL;
structmy_data *md2=NULL;
md2=init_data(md2);
md=init_data(md);
md2->value=20;
md->value=10;
/*第一種方式:使用統默認的workqueue隊列——keventd_wq,直接調度*/
INIT_WORK(&md->my_work,work_func);
schedule_work(&md->my_work);
/*第二種方式:創建自己的工作隊列,加入工作到工作隊列(加入內核就對其調度執行)*/
wq=create_workqueue("test");
INIT_WORK(&md2->my_work,work_func);
queue_work(wq,&md2->my_work);
return0;
}
static void work_exit(void)
{
/*工作隊列銷毀*/
destroy_workqueue(wq);
}
module_init(work_init);
module_exit(work_exit);