1. 什么是workqueue
?????? Linux中的Workqueue機(jī)制就是為了簡(jiǎn)化內(nèi)核線程的創(chuàng)建。通過(guò)調(diào)用workqueue的接口就能創(chuàng)建內(nèi)核線程。并且可以根據(jù)當(dāng)前系統(tǒng)CPU的個(gè)數(shù)創(chuàng)建線程的數(shù)量,使得線程處理的事務(wù)能夠并行化。workqueue是內(nèi)核中實(shí)現(xiàn)簡(jiǎn)單而有效的機(jī)制,他顯然簡(jiǎn)化了內(nèi)核daemon的創(chuàng)建,方便了用戶的編程.
????? 工作隊(duì)列(workqueue)是另外一種將工作推后執(zhí)行的形式.工作隊(duì)列可以把工作推后,交由一個(gè)內(nèi)核線程去執(zhí)行,也就是說(shuō),這個(gè)下半部分可以在進(jìn)程上下文中執(zhí)行。最重要的就是工作隊(duì)列允許被重新調(diào)度甚至是睡眠。
2. 數(shù)據(jù)結(jié)構(gòu)
???? 我們把推后執(zhí)行的任務(wù)叫做工作(work),描述它的數(shù)據(jù)結(jié)構(gòu)為work_struct:
struct?work_struct?{??
????atomic_long_t?data;???????/*工作處理函數(shù)func的參數(shù)*/??
#define?WORK_STRUCT_PENDING?0????????/*?T?if?work?item?pending?execution?*/??
#define?WORK_STRUCT_STATIC?1????????/*?static?initializer?(debugobjects)?*/??
#define?WORK_STRUCT_FLAG_MASK?(3UL)??
#define?WORK_STRUCT_WQ_DATA_MASK?(~WORK_STRUCT_FLAG_MASK)??
????struct?list_head?entry;????????/*連接工作的指針*/??
????work_func_t?func;??????????????/*工作處理函數(shù)*/??
#ifdef?CONFIG_LOCKDEP??
????struct?lockdep_map?lockdep_map;??
#endif??
};??
????? 這些工作以隊(duì)列結(jié)構(gòu)組織成工作隊(duì)列(workqueue),其數(shù)據(jù)結(jié)構(gòu)為workqueue_struct:
[cpp]?view plain?copy
struct?workqueue_struct?{??
?struct?cpu_workqueue_struct?*cpu_wq;??
?struct?list_head?list;??
?const?char?*name;???/*workqueue?name*/??
?int?singlethread;???/*是不是單線程?-?單線程我們首選第一個(gè)CPU?-0表示采用默認(rèn)的工作者線程event*/??
?int?freezeable;??/*?Freeze?threads?during?suspend?*/??
?int?rt;??
};???
???? 如果是多線程,Linux根據(jù)當(dāng)前系統(tǒng)CPU的個(gè)數(shù)創(chuàng)建cpu_workqueue_struct 其結(jié)構(gòu)體就是:
[cpp]?view plain?copy
truct?cpu_workqueue_struct?{??
?spinlock_t?lock;/*因?yàn)楣ぷ髡呔€程需要頻繁的處理連接到其上的工作,所以需要枷鎖保護(hù)*/??
?struct?list_head?worklist;??
?wait_queue_head_t?more_work;??
?struct?work_struct?*current_work;?/*當(dāng)前的work*/??
?struct?workqueue_struct?*wq;???/*所屬的workqueue*/??
?struct?task_struct?*thread;?/*任務(wù)的上下文*/??
}?____cacheline_aligned;??
?????? 在該結(jié)構(gòu)主要維護(hù)了一個(gè)任務(wù)隊(duì)列,以及內(nèi)核線程需要睡眠的等待隊(duì)列,另外還維護(hù)了一個(gè)任務(wù)上下文,即task_struct。
?????? 三者之間的關(guān)系如下:
3. 創(chuàng)建工作
3.1 創(chuàng)建工作queue
a. create_singlethread_workqueue(name)
??????? 該函數(shù)的實(shí)現(xiàn)機(jī)制如下圖所示,函數(shù)返回一個(gè)類型為struct workqueue_struct的指針變量,該指針變量所指向的內(nèi)存地址在函數(shù)內(nèi)部調(diào)用kzalloc動(dòng)態(tài)生成。所以driver在不再使用該work queue的情況下調(diào)用:
??????? void destroy_workqueue(struct workqueue_struct *wq)來(lái)釋放此處的內(nèi)存地址。
??????? 圖中的cwq是一per-CPU類型的地址空間。對(duì)于create_singlethread_workqueue而言,即使是對(duì)于多CPU系統(tǒng),內(nèi)核也只負(fù)責(zé)創(chuàng)建一個(gè)worker_thread內(nèi)核進(jìn)程。該內(nèi)核進(jìn)程被創(chuàng)建之后,會(huì)先定義一個(gè)圖中的wait節(jié)點(diǎn),然后在一循環(huán)體中檢查cwq中的worklist,如果該隊(duì)列為空,那么就會(huì)把wait節(jié)點(diǎn)加入到cwq中的more_work中,然后休眠在該等待隊(duì)列中。
??????? Driver調(diào)用queue_work(struct workqueue_struct *wq, struct work_struct *work)向wq中加入工作節(jié)點(diǎn)。work會(huì)依次加在cwq->worklist所指向的鏈表中。queue_work向cwq->worklist中加入一個(gè)work節(jié)點(diǎn),同時(shí)會(huì)調(diào)用wake_up來(lái)喚醒休眠在cwq->more_work上的worker_thread進(jìn)程。wake_up會(huì)先調(diào)用wait節(jié)點(diǎn)上的autoremove_wake_function函數(shù),然后將wait節(jié)點(diǎn)從cwq->more_work中移走。
??????? worker_thread再次被調(diào)度,開始處理cwq->worklist中的所有work節(jié)點(diǎn)...當(dāng)所有work節(jié)點(diǎn)處理完畢,worker_thread重新將wait節(jié)點(diǎn)加入到cwq->more_work,然后再次休眠在該等待隊(duì)列中直到Driver調(diào)用queue_work...
b. create_workqueue
???????相對(duì)于create_singlethread_workqueue, create_workqueue同樣會(huì)分配一個(gè)wq的工作隊(duì)列,但是不同之處在于,對(duì)于多CPU系統(tǒng)而言,對(duì)每一個(gè)CPU,都會(huì)為之創(chuàng)建一個(gè)per-CPU的cwq結(jié)構(gòu),對(duì)應(yīng)每一個(gè)cwq,都會(huì)生成一個(gè)新的worker_thread進(jìn)程。但是當(dāng)用queue_work向cwq上提交work節(jié)點(diǎn)時(shí),是哪個(gè)CPU調(diào)用該函數(shù),那么便向該CPU對(duì)應(yīng)的cwq上的worklist上增加work節(jié)點(diǎn)。
c.小結(jié)
?????? 當(dāng)用戶調(diào)用workqueue的初始化接口create_workqueue或者create_singlethread_workqueue對(duì)workqueue隊(duì)列進(jìn)行初始化時(shí),內(nèi)核就開始為用戶分配一個(gè)workqueue對(duì)象,并且將其鏈到一個(gè)全局的workqueue隊(duì)列中。然后Linux根據(jù)當(dāng)前CPU的情況,為workqueue對(duì)象分配與CPU個(gè)數(shù)相同的cpu_workqueue_struct對(duì)象,每個(gè)cpu_workqueue_struct對(duì)象都會(huì)存在一條任務(wù)隊(duì)列。緊接著,Linux為每個(gè)cpu_workqueue_struct對(duì)象分配一個(gè)內(nèi)核thread,即內(nèi)核daemon去處理每個(gè)隊(duì)列中的任務(wù)。至此,用戶調(diào)用初始化接口將workqueue初始化完畢,返回workqueue的指針。
??????? workqueue初始化完畢之后,將任務(wù)運(yùn)行的上下文環(huán)境構(gòu)建起來(lái)了,但是具體還沒有可執(zhí)行的任務(wù),所以,需要定義具體的work_struct對(duì)象。然后將work_struct加入到任務(wù)隊(duì)列中,Linux會(huì)喚醒daemon去處理任務(wù)。
?????? 上述描述的workqueue內(nèi)核實(shí)現(xiàn)原理可以描述如下:
3.2? 創(chuàng)建工作
?????? 要使用工作隊(duì)列,首先要做的是創(chuàng)建一些需要推后完成的工作??梢酝ㄟ^(guò)DECLARE_WORK在編譯時(shí)靜態(tài)地建該結(jié)構(gòu):
?????? DECLARE_WORK(name,void (*func) (void *), void *data);
????? 這樣就會(huì)靜態(tài)地創(chuàng)建一個(gè)名為name,待執(zhí)行函數(shù)為func,參數(shù)為data的work_struct結(jié)構(gòu)。
????? 同樣,也可以在運(yùn)行時(shí)通過(guò)指針創(chuàng)建一個(gè)工作:
??????INIT_WORK(structwork_struct *work, woid(*func) (void *), void *data);
4. 調(diào)度
a. schedule_work
?????? 在大多數(shù)情況下, 并不需要自己建立工作隊(duì)列,而是只定義工作, 將工作結(jié)構(gòu)掛接到內(nèi)核預(yù)定義的事件工作隊(duì)列中調(diào)度, 在kernel/workqueue.c中定義了一個(gè)靜態(tài)全局量的工作隊(duì)列static struct workqueue_struct *keventd_wq;默認(rèn)的工作者線程叫做events/n,這里n是處理器的編號(hào),每個(gè)處理器對(duì)應(yīng)一個(gè)線程。比如,單處理器的系統(tǒng)只有events/0這樣一個(gè)線程。而雙處理器的系統(tǒng)就會(huì)多一個(gè)events/1線程。
?????? 調(diào)度工作結(jié)構(gòu), 將工作結(jié)構(gòu)添加到全局的事件工作隊(duì)列keventd_wq,調(diào)用了queue_work通用模塊。對(duì)外屏蔽了keventd_wq的接口,用戶無(wú)需知道此參數(shù),相當(dāng)于使用了默認(rèn)參數(shù)。keventd_wq由內(nèi)核自己維護(hù),創(chuàng)建,銷毀。這樣work馬上就會(huì)被調(diào)度,一旦其所在的處理器上的工作者線程被喚醒,它就會(huì)被執(zhí)行。
b. schedule_delayed_work(&work,delay);
????? 有時(shí)候并不希望工作馬上就被執(zhí)行,而是希望它經(jīng)過(guò)一段延遲以后再執(zhí)行。在這種情況下,同時(shí)也可以利用timer來(lái)進(jìn)行延時(shí)調(diào)度,到期后才由默認(rèn)的定時(shí)器回調(diào)函數(shù)進(jìn)行工作注冊(cè)。延遲delay后,被定時(shí)器喚醒,將work添加到工作隊(duì)列wq中。
????? 工作隊(duì)列是沒有優(yōu)先級(jí)的,基本按照FIFO的方式進(jìn)行處理。
?5. 示例
[cpp]?view plain?copy
#include???
#include???
#include???
static?struct?workqueue_struct?*queue=NULL;??
static?struct?work_struct???work;??
staticvoid?work_handler(struct?work_struct?*data)??
{??
???????printk(KERN_ALERT"work?handler?function.\n");??
}??
static?int?__init?test_init(void)??
{??
??????queue=create_singlethread_workqueue("hello?world");/*創(chuàng)建一個(gè)單線程的工作隊(duì)列*/??
??????if?(!queue)??
????????????goto?err;??
???????INIT_WORK(&work,work_handler);??
???????schedule_work(&work);??
??????return0;??
err:??
??????return-1;??
}??
static???void?__exit?test_exit(void)??
{??
???????destroy_workqueue(queue);??
}??
MODULE_LICENSE("GPL");??
module_init(test_init);??
module_exit(test_exit); ?
序號(hào)
接口函數(shù)
說(shuō)明
1
create_workqueue
用于創(chuàng)建一個(gè)workqueue隊(duì)列,為系統(tǒng)中的每個(gè)CPU都創(chuàng)建一個(gè)內(nèi)核線程。輸入?yún)?shù):
@name:workqueue的名稱
2
create_singlethread_workqueue
用于創(chuàng)建workqueue,只創(chuàng)建一個(gè)內(nèi)核線程。輸入?yún)?shù):
@name:workqueue名稱
3
destroy_workqueue
釋放workqueue隊(duì)列。輸入?yún)?shù):
@ workqueue_struct:需要釋放的workqueue隊(duì)列指針
4
schedule_work
調(diào)度執(zhí)行一個(gè)具體的任務(wù),執(zhí)行的任務(wù)將會(huì)被掛入Linux系統(tǒng)提供的workqueue——keventd_wq輸入?yún)?shù):
@ work_struct:具體任務(wù)對(duì)象指針
5
schedule_delayed_work
延遲一定時(shí)間去執(zhí)行一個(gè)具體的任務(wù),功能與schedule_work類似,多了一個(gè)延遲時(shí)間,輸入?yún)?shù):
@work_struct:具體任務(wù)對(duì)象指針
@delay:延遲時(shí)間
6
queue_work
調(diào)度執(zhí)行一個(gè)指定workqueue中的任務(wù)。輸入?yún)?shù):
@ workqueue_struct:指定的workqueue指針
@work_struct:具體任務(wù)對(duì)象指針
7
queue_delayed_work
延遲調(diào)度執(zhí)行一個(gè)指定workqueue中的任務(wù),功能與queue_work類似,輸入?yún)?shù)多了一個(gè)delay。
?
評(píng)論
查看更多