1. 有些時候核心需要一個異步的程序執行上下文,而工作
隊列(workqueue)可以滿足這種需求。
工作隊列中的每一個元素都是一個工作項(work item),
有一個函數與工作項相關,這個函數就是工作項所要處
理的任務。
核心中有一個專門的線程——被稱作worker,來依次執行
工作隊列中的每一個工作項對應的函數,當工作隊列為
空時,這個worker就變為空閑狀态(idle),當有新的
工作項加入到工作隊列時,worker又重新開始執行。
2. 在最早的實作中包括兩種實作方式,一種是整個系統隻
有一個worker(single thread,ST),另一種是每個
CPU包含一個worker(multiple thread,MT),每一個
CPU包含一個屬于自己的worker pool 。
這兩種實作都引起了系統中對于這種異步上下文的競争,
隻不過是MT方式的競争可能更小一些。
是以,核心人員對workqueue作了重新實作,新的實作
被稱作concurrency managed workqueue(cmwq),新的
實作的特點如下:
* 與之前的API相容
* 實作了統一的每CPU worker pool,減少了資源的浪
費,提高了并發的靈活性
* 可以自動調節worker pool和并發的級别
3. 為了簡化執行這種異步上下文 ,引入了work item,它
是一個簡單的結構體,包含一個函數指針,這個指針指
向的函數就是需要在異步上文中執行的函數。
當一個核心子系統或者驅動程式需要在異步上下文中執
行一個函數時,它首先需要建立一個work item結構體,
然後将這個結構體加入到工作隊列中。
工作者線程(worker thread)負責從工作對列中取出work
item并執行,直到工作隊列中的work item為空。工作
者線程是由worker-pool管理的。
cmwq的實作在使用者接口(即子系統或者驅動程式的使用)
和背景支援上(即如何管理worker pool以及處理work
item)有差别。
每一個CPU上都有兩個worker pool,一個是用來處理普
通的work item,另一個是用來處理高優先級的work item。
另外還有一些worker pool用來處理未添加到綁定到CPU
的wq上的work item,這些worker pool的數量是動态的。
可以通過修改工作隊列的屬性來改變添加到其上的work
item的執行行為,例如在哪個CPU上執行、并發限制、優
先級等。
當一個work item添加到workqueue時,根據函數的參數
以及要添加到的工作隊列的屬性就可以确定将由哪一個
worker pool來執行,并且會将該work item添加到該
worker pool共享的工作清單中。例如,當一個work
item被添加到一個workqueue時,它要麼被添加到普通
的worker-pool的工作清單,要麼被添加到高優先級的
worker-pool的工作清單,這裡的兩個worker-pool對應
于添加work item到工作隊列的CPU。
管理一個工作者線程池的并發度一直是工作者線程池面
臨的一個重要的問題。cmwq保持了最小的并發度,但是
又讓CPU不會空閑,充分利用了CPU資源。
每一個綁定到CPU的線程池通過在排程器中添加鈎子實
現了并發的控制。當一個工作項被喚醒或者睡眠的時候,
工作者線程池都會接到通知,以此來追蹤目前并發的工
作者線程數。一般來說,當一個CPU上有一個或者多個
工作者線程在執行的時候,與該CPU綁定的工作者線程
池不會再起動其他的工作者線程,當該CPU上最後一個
工作者線程睡眠後,立刻啟動一個新的工作,來保證CPU
不會空轉。
對于未綁定到CPU的工作隊列來說,它的線程池數量是
動态的。可以通過apply_workqueue_attrs函數來設定這
個未綁定的工作隊列的參數,系統會自動生成與這些參
數對應的工作者線程池。另外對于綁定的工作隊列可以
設定某些參數,讓某個工作隊列忽略并發的限制。
任何需要多個工作者線程同時執行的子系統或者驅動程
序都需要使用有急救工作者線程(rescuer worker)的
工作隊列。例如,在記憶體回收的時候,回收記憶體的工作
項往往需要同時執行,是以如果沒有使用這種工作隊列
的話,就會造成死鎖,因為後執行的工作者線程會等待
前面的工作者線程被釋放。
4. 應用程式接口(API)
alloc_workqueue負責建立一個工作隊列,之前的create_*workqueue
之類的接口已經被丢棄了。該函數有三個參數@name,
@flags以及@max_active,@name表示該工作隊列的名字,
如果急救工作者線程也存在的話,那麼@name也是該急救
工作者線程的名字。
工作隊列已經不再管理執行資源,但是它作為一個域用
來管理工作項,例如保證工作項往前執行、flush以及
工作項的屬性。@flags和@max_active參數控制工作項
的執行資源配置設定,如何被排程以及如何執行。
@flags:
WQ_UNBOUND
未綁定的工作隊列對應的工作者線程池數是動态的。該
工作者線程池未綁定到任何的CPU,是以相當于是一個簡
單的執行上下文的提供者,它會立即執行添加到該工作
隊列工作項。一般在下面兩種情況會用到:
* 工作者線程的并發級别波動很大,且将工作項添加到
工作隊列的發起者會在不同的CPU之間來回執行,因
此如果不使用未綁定工作隊列的話,就會造成在各個
CPU的工作者線程池上建立了許多不會使用的工作者
線程。
* 當CPU的負擔很重時,最好将工作項添加到位綁定的工
作隊列,由排程器來進行排程。
WQ_FREEZABLE
當系統暫停時,工作隊列會進入到當機狀态。并且會清
空該工作隊列中的工作項,直到被解凍才可以執行新的
工作。
WQ_MEM_RECLAIM
任何可能在記憶體回收路徑上使用的工作隊列都必須設定
該标記,它保證了不管記憶體壓力有多大,都至少有一個
執行上下文與之對應。
WQ_HIGHPRI
高優先級工作隊列的工作項被加入到相應CPU的高優先
級工作者線程池。高優先級工作者線程池由提高了nice
級别的線程來服務。
普通的工作者線程池和高優先級的工作者線程池是隔離
的,他們之間不進行任何的互動。對于并發級别的控制
也是各自獨立進行控制的。
WQ_CPU_INTENSIVE
這種工作隊列中的工作項并不受并發級别的控制,因為
該類型的工作項通常會需要很多的CPU使用量,是以最
好的辦法就是由系統排程器來進行排程。
另外,并發級别的限制會影響密集型工作項的執行,例
如目前正在運作的非密集型的工作會延遲密集型工作的
執行。
該标記對未綁定的工作隊列是無效的。
@max_active:
表示每個工作隊列同時最多能有幾個工作項在同一個CPU
上運作。例如,max_active=16,表示同時最多能有16個
該工作隊列的工作項在每一個CPU上運作。
對于綁定的工作隊列,@max_active的最大值為512,默
認情況下,該參數傳值為0,此時它的最大值為256。對
于未綁定的工作隊列,max_active的最大值要大于512。
工作隊列的同時處于活躍狀态的工作項的數目是由使用者
調節的,例如使用者同時添加到工作隊列中的工作項的數
目。除非有需求調節活躍工作項的數目,否則,推薦該
參數指派為0。
有些需要依賴ST工作隊列的順序,是以可以使WQ_UNBOUND
和@max_active為1。這樣就模拟了ST,每一個該類型的
工作項都應該被添加到未綁定的工作隊列中,且一次隻
能有一個工作項執行就限定了執行的順序。
5. 指導方針
* 如果工作隊列中的工作項可能用在記憶體回收代碼路徑
中,一定要設定WQ_MEM_RECLAIM,每一個該類型的隊
列都有一個保留的執行上下文。另外,如果在記憶體回
收路徑中有多個工作項互相依賴的話,應該将這些工
作項添加到不同的工作對列中。
* 如果沒有特殊要求的話,推薦@max_active參數的值
為0。
* 一般如果沒有執行次序要求的話,不會用到ST。
* 工作隊列作為工作項一個域,用來WQ_MEM_RECLAIM,
flush以及某些工作項的共有屬性。如果工作項不會
用到上面的任何一個特性,可以使用系統工作隊列。
* 除非一個工作項要耗費很多的CPU,一般将工作項添
加到綁定的工作隊列中。
ref
===
1. Documentation/workqueue.txt