Linux workqueue工作原理 【转】
?
轉自:http://blog.chinaunix.net/uid-21977330-id-3754719.html
轉自:http://bgutech.blog.163.com/blog/static/18261124320116181119889/
1. 什么是workqueue
?????? Linux中的Workqueue機制就是為了簡化內核線程的創建。通過調用workqueue的接口就能創建內核線程。并且可以根據當前系統CPU的個 數創建線程的數量,使得線程處理的事務能夠并行化。workqueue是內核中實現簡單而有效的機制,他顯然簡化了內核daemon的創建,方便了用戶的 編程.
????? 工作隊列(workqueue)是另外一種將工作推后執行的形式.工作隊列可以把工作推后,交由一個內核線程去執行,也就是說,這個下半部分可以在進程上下文中執行。最重要的就是工作隊列允許被重新調度甚至是睡眠。
????? 那么,什么情況下使用工作隊列,什么情況下使用tasklet。如果推后執行的任務需要睡眠,那么就選擇工作隊列。如果推后執行的任務不需要睡眠,那么就 選擇tasklet。另外,如果需要用一個可以重新調度的實體來執行你的下半部處理,也應該使用工作隊列。它是唯一能在進程上下文運行的下半部實現的機 制,也只有它才可以睡眠。這意味著在需要獲得大量的內存時、在需要獲取信號量時,在需要執行阻塞式的I/O操作時,它都會非常有用。如果不需要用一個內核 線程來推后執行工作,那么就考慮使用tasklet。
2. 數據結構
???? 我們把推后執行的任務叫做工作(work),描述它的數據結構為work_struct:
?
struct work_struct {atomic_long_t data; /*工作處理函數func的參數*/ #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; /*工作處理函數*/ #ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map; #endif };?
????? 這些工作以隊列結構組織成工作隊列(workqueue),其數據結構為workqueue_struct:
struct workqueue_struct {struct cpu_workqueue_struct *cpu_wq;struct list_head list;const char *name; /*workqueue name*/int singlethread; /*是不是單線程 - 單線程我們首選第一個CPU -0表示采用默認的工作者線程event*/int freezeable; /* Freeze threads during suspend */int rt; };
???? 如果是多線程,Linux根據當前系統CPU的個數創建cpu_workqueue_struct 其結構體就是:
?????? 在該結構主要維護了一個任務隊列,以及內核線程需要睡眠的等待隊列,另外還維護了一個任務上下文,即task_struct。
?????? 三者之間的關系如下:
?
3. 創建工作
3.1 創建工作queue
a. create_singlethread_workqueue(name)
??????? 該函數的實現機制如下圖所示,函數返回一個類型為struct workqueue_struct的指針變量,該指針變量所指向的內存地址在函數內部調用kzalloc動態生成。所以driver在不再使用該work queue的情況下調用:
??????? void destroy_workqueue(struct workqueue_struct *wq)來釋放此處的內存地址。
?
??????? 圖中的cwq是一per-CPU類型的地址空間。對于create_singlethread_workqueue而言,即使是對于多CPU系統,內核也 只負責創建一個worker_thread內核進程。該內核進程被創建之后,會先定義一個圖中的wait節點,然后在一循環體中檢查cwq中的 worklist,如果該隊列為空,那么就會把wait節點加入到cwq中的more_work中,然后休眠在該等待隊列中。
??????? Driver調用queue_work(struct workqueue_struct *wq, struct work_struct *work)向wq中加入工作節點。work會依次加在cwq->worklist所指向的鏈表中。queue_work向 cwq->worklist中加入一個work節點,同時會調用wake_up來喚醒休眠在cwq->more_work上的 worker_thread進程。wake_up會先調用wait節點上的autoremove_wake_function函數,然后將wait節點從 cwq->more_work中移走。
??????? worker_thread再次被調度,開始處理cwq->worklist中的所有work節點...當所有work節點處理完 畢,worker_thread重新將wait節點加入到cwq->more_work,然后再次休眠在該等待隊列中直到Driver調用 queue_work...
b. create_workqueue
?
?
?
???????相對于create_singlethread_workqueue, create_workqueue同樣會分配一個wq的工作隊列,但是不同之處在于,對于多CPU系統而言,對每一個CPU,都會為之創建一個per- CPU的cwq結構,對應每一個cwq,都會生成一個新的worker_thread進程。但是當用queue_work向cwq上提交work節點時, 是哪個CPU調用該函數,那么便向該CPU對應的cwq上的worklist上增加work節點。
c.小結
?????? 當用戶調用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初始化完畢之后,將任務運行的上下文環境構建起來了,但是具體還沒有可執行的任務,所以,需要定義具體的work_struct對象。然后將work_struct加入到任務隊列中,Linux會喚醒daemon去處理任務。
?????? 上述描述的workqueue內核實現原理可以描述如下:
?
?
3.2? 創建工作
?????? 要使用工作隊列,首先要做的是創建一些需要推后完成的工作。可以通過DECLARE_WORK在編譯時靜態地建該結構:
?????? DECLARE_WORK(name,void (*func) (void *), void *data);
????? 這樣就會靜態地創建一個名為name,待執行函數為func,參數為data的work_struct結構。
????? 同樣,也可以在運行時通過指針創建一個工作:
??????INIT_WORK(structwork_struct *work, woid(*func) (void *), void *data);
4. 調度
a. schedule_work
?????? 在大多數情況下, 并不需要自己建立工作隊列,而是只定義工作, 將工作結構掛接到內核預定義的事件工作隊列中調度, 在kernel/workqueue.c中定義了一個靜態全局量的工作隊列static struct workqueue_struct *keventd_wq;默認的工作者線程叫做events/n,這里n是處理器的編號,每個處理器對應一個線程。比如,單處理器的系統只有events /0這樣一個線程。而雙處理器的系統就會多一個events/1線程。
?????? 調度工作結構, 將工作結構添加到全局的事件工作隊列keventd_wq,調用了queue_work通用模塊。對外屏蔽了keventd_wq的接口,用戶無需知道此 參數,相當于使用了默認參數。keventd_wq由內核自己維護,創建,銷毀。這樣work馬上就會被調度,一旦其所在的處理器上的工作者線程被喚醒, 它就會被執行。
b. schedule_delayed_work(&work,delay);
????? 有時候并不希望工作馬上就被執行,而是希望它經過一段延遲以后再執行。在這種情況下,同時也可以利用timer來進行延時調度,到期后才由默認的定時器回調函數進行工作注冊。延遲delay后,被定時器喚醒,將work添加到工作隊列wq中。
?????工作隊列是沒有優先級的,基本按照FIFO的方式進行處理。
5. work queue API
1. create_workqueue用于創建一個workqueue隊列,為系統中的每個CPU都創建一個內核線程。輸入參數:
@name:workqueue的名稱
2. create_singlethread_workqueue用于創建workqueue,只創建一個內核線程。輸入參數:
@name:workqueue名稱
3.?destroy_workqueue釋放workqueue隊列。輸入參數:
@ workqueue_struct:需要釋放的workqueue隊列指針
4. schedule_work調度執行一個具體的任務,執行的任務將會被掛入Linux系統提供的workqueue——keventd_wq輸入參數:
@ work_struct:具體任務對象指針
5.?schedule_delayed_work延遲一定時間去執行一個具體的任務,功能與schedule_work類似,多了一個延遲時間,輸入參數:
@work_struct:具體任務對象指針
@delay:延遲時間
6. queue_work調度執行一個指定workqueue中的任務。輸入參數:
@ workqueue_struct:指定的workqueue指針
@work_struct:具體任務對象指針
7. queue_delayed_work延遲調度執行一個指定workqueue中的任務,功能與queue_work類似,輸入參數多了一個delay。
?
6.?示例
?
在driver 程序中許多很多情況需要設置延后執行的,這樣工作隊列就很好幫助我們實現。
總結
以上是生活随笔為你收集整理的Linux workqueue工作原理 【转】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iOS - Frame 项目架构
- 下一篇: 多个openstack合并成一个open