接著上節的來,我們在上節說了軟中斷和tasklet,那這最後就是工作佇列了哦..
工作佇列和前面討論的其他形式都不相同,它可以把工作推後,交由乙個核心執行緒去執行----該工作總是會在程序上下文執行。這樣,通過工作佇列執行**能佔盡程序上下文的所有優勢,最重要的就是工作佇列允許重新排程甚至是睡眠。相比較前邊兩個,這個選擇起來就很容易了。我說過,前邊兩個是不允許休眠的,這個是允許休眠的,這就很明白了是不?這意味著在你需要獲得大量記憶體的時候,在你需要獲取訊號量時,在你需要執行阻塞式的i/o操作時,它都會非常有用(先說話, 這個不是我說的,是書上這麼說的哦)。
工作佇列子系統是乙個用於建立核心執行緒的介面,通過它建立的程序負責執行由核心其他部分排到佇列裡的任務。它建立的這些核心執行緒被稱作工作者執行緒(worker threads).工作佇列可以讓你的驅動程式建立乙個專門的工作者執行緒來處理需要推後的工作。不過,工作佇列子系統提供了乙個預設的工作者執行緒來處理這些工作。因此,工作佇列最基本的表現形式就轉變成乙個把需要推後執行的任務交給特定的通用執行緒這樣一種介面。預設的工作執行緒叫做event/n.每個處理器對應乙個執行緒,這裡的n代表了處理器編號。除非乙個驅動程式或者子系統必須建立乙個屬於自己的核心執行緒,否則最好還是使用預設執行緒。
1.工作這執行緒結構用下面的結構表示:12
3struct
workqueue_struct
結構中陣列的每一項對應系統的乙個cpu.接下來,在看看在kernel/workqueue.c中的核心資料結構cpu_workqueue_struct:12
3456
78910
struct
cpu_workqueue_struct
2.表示工作的資料結構:所有的工作者執行緒都是用普通的核心執行緒來實現的,它們都要執行worker_thread()函式。在它初始化完以後,這個函式執行乙個死迴圈執行乙個迴圈並開始休眠,當有操作被插入到佇列的時候,執行緒就會被喚醒,以便執行這些操作。當沒有剩餘的時候,它又會繼續休眠。工作有work_struct(linux/workqueue)結構表示:12
3456
78struct
work_struct
當乙個工作執行緒被喚醒時,它會執行它的鍊錶上的所有工作。工作一旦執行完畢,它就將相應的work_struct物件從鍊錶上移去,當鍊表不再有物件時,它就繼續休眠。woker_thread函式的核心流程如下:12
3456
78910
11for
(;;)
分析一下上面的**。首先執行緒將自己設定為休眠狀態並把自己加入等待佇列。如果工作對列是空的,執行緒呼叫schedule()函式進入睡眠狀態。如果鍊錶有物件,執行緒就將自己設為執行態,脫離等待佇列。然後,再次呼叫run_workqueue()執行推後的工作。好了,接下來,問題就糾結在run_workqueue(),它完成實際推後到此的工作:12
3456
78while
(!list_empty(&cwq->worklist))
該函式迴圈遍歷鍊錶上每個待處理的工作,執行鍊錶上每個結點上的work_struct的func成員函式:
1.當鍊表不為空時,選取下乙個節點物件。
2.獲取我們希望執行的函式func及其引數data。
3.把該結點從鍊錶上接下來,將待處理標誌位pending清0。
4.呼叫函式。
5.重複執行。
老師說的好:光說不練,不是好漢。現在我們繼續來看看怎麼用吧:
1.首先,實際建立一些需要推後完成的工作,可以在編譯時靜態地建立該資料結構:
1declare_work(name,
void
(*func)(
void
*),
void
*data);
當然了,如果願意,我們當然可以在執行時通過指標動態建立乙個工作:
1init_work(
struct
work_struct *work,
void
(*func)(
void
*),
void
*data);
2.工作佇列處理函式,會由乙個工作者執行緒執行,因此,函式會執行在程序上下文中,預設情況下,允許相應中斷,並且不持有鎖。如果需要,函式可以睡眠。需要注意的是,儘管處理函式執行在程序上下文中,但它不能訪問使用者空間,因為核心執行緒在使用者空間沒有相應的記憶體對映。函式原型如下:
1void
work_hander(
void
*data);
3.對工作進行排程。前面的準備工作做完以後,下面就可以開始排程了,只需呼叫schedule_work(&work).這樣work馬上就會被排程,一旦其所在的處理器上的工作者執行緒被喚醒,它就會被執行。當然如果不想快速執行,而是想延遲一段時間執行,按就用schedule_delay_work(&work,delay);delay是要延遲的時間節拍,後面講.
4.重新整理操作。插入佇列的工作會在工作者執行緒下一次被喚醒的時候執行。有時,在繼續下一步工作之前,你必須保證一些操作已經執行完畢等等。由於這些原因,核心提供了乙個用於重新整理指定工作佇列的函式:void flush_scheduled_work(void); 這個函式會一直等待,直到佇列中所有的物件都被執行後才返回。在等待所有待處理的工作執行的時候,該函式會進入休眠狀態,所以只能在程序上下文中使用它。需要說明的是,該函式並不取消任何延遲執行的工作。取消延遲執行的工作應該呼叫:int cancel_delayed_work(struct work_struct *work);這個函式可以取消任何與work_struct 相關掛起的工作。
5.建立新的工作佇列。前邊說過最好使用預設執行緒,可如果你堅持要使用自己建立的執行緒,咋辦?這時你就應該建立乙個新的工作佇列和與之相應的工作者執行緒,方法很簡單,用下面的函式:struct workqueue_struct *create_workqueue(const char *name);name是新核心執行緒的名字。這樣就會建立所有的工作者執行緒(系統中的每個處理器都有乙個)並且做好所有開始處理工作之前的準備工作。在建立之後,就呼叫下面的函式吧:12
int
queue_work(
struct
workqueue_struct *wq,
struct
work_struct *work);
int
queue_delayed_work(
struct
workqueue_struct *wq,
struct
work_struct *work,unsigned
long
delay);
這兩個函式和schedule_work()和schedule_delayed_work()相近,唯一的區別在於它們可以針對特定的工作佇列而不是預設的event佇列進行操作。
好了,工作佇列也說完了,我還是結合前邊一篇,把這三個地板不實現的策略比較一下,方便以後選擇.
首先,tasklet是基於軟中斷實現的,兩者相近,工作佇列機制與它們完全不同,靠核心執行緒來實現。軟中斷提供的序列化的保障最少,這就要求中斷處理函式必須格外小心地採取一些步驟確保共享資料的安全,兩個甚至更多相同類別的軟中斷有可能在不同的處理器上同時執行。如果被考察的**本身多線索化的工作做得非常好,它完全使用單處理器變數,那麼軟中斷就是非常好的選擇。對於時間要求嚴格和執行效率很高的應用來說,它執行的也最快。否則選擇tasklets意義更大。tasklet介面簡單,而且兩種同種型別的tasklet不能同時執行,所以實現起來也會簡單一些。如果需要把任務推遲到程序上下文中完成,那你只能選擇工作佇列了。如果不需要休眠,那軟中斷和tasklet可能更合適。另外就是工作佇列造成的開銷最大,當然這是相對的,針對大部分情況,工作佇列都能提供足夠的支援。從方便度上考慮就是:工作佇列,tasklets,最後才是軟中斷。我們在做驅動的時候,關於這三個下半部實現,需要考慮兩點:首先,是不是需要乙個可排程的實體來執行需要推後完成的工作(即休眠的需要),如果有,工作佇列就是唯一的選擇,否則最好用tasklet。效能如果是最重要的,那還是軟中斷吧。
函式描述
void local_bh_disable()
禁止本地處理器的軟中斷和tasklet的處理
void local_bh_enable()
啟用本地處理器的軟中斷和tasklet的處理
這些函式有可能被巢狀使用----最後被呼叫的local_bh_enable()最終啟用下半部。函式通過preempt_count為每個程序維護乙個計數器。當計數器變為0時,下半部才能夠被處理。因為下半部的處理已經被禁止了,所以local_bh_enable()還需要檢查所有現存的待處理的下半部並執行它們。
好了,這一次講完了,畫了兩次,我們在這兩次中提到了一些同時發生的問題,這時可能存在資料共享互斥訪問的問題,這個就是核心同步方面的事情了,我們後面再慢慢說這個事。
linux中斷的上半部和下半部
本文 與linux中斷息息相關的乙個重要概念是linux中斷分為兩個半部 上半部 tophalf 和下半部 bottom half 上半部的功能是 登記中斷 當乙個中斷發生時,它進行相應地硬體讀寫後就把中斷例程的下半部掛到該裝置的下半部執行佇列中去。因此,上半部執行的速度就會很快,可以服務更多的中斷...
Linux中斷上半部和下半部概念
前言 cpu在執行程式時,如果有外部中斷觸發時,如定時器中斷 序列匯流排中斷等,cpu停止當前任務從 而轉去響應中斷處理。對於中斷函式的處理,原則是盡快處理完事務並退出中斷,這一點也比較好 理解,盡快處理中斷並返回,保證正常任務的執行,並且能否響應其他事務的中斷,保證實時性和 併發性。其實,在微控制...
linux中斷處理的上半部和下半部
裝置的中斷會打斷核心中程序的正常排程和執行,系統對更高吞吐率的追求勢必 要求中斷服務程式盡可能地短小精悍。但是,這個良好的願望往往與現實並不吻合。在大多數真實的系統中,當中斷到來時,要完成的工作往往並不會是短小的,它可能 要進行較大量的耗時處理。為了在中斷執行時間盡可能短和中斷處理需完成大量工作之間...