1. 程式人生 > >Linux內核實踐之工作隊列

Linux內核實踐之工作隊列

測試數據 daemon 定義 ifd 不同 name auth 過程 上下

工作隊列(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);

實驗結果:

技術分享

Linux內核實踐之工作隊列