linux核心分析筆記—-上半部與下半部(下)

來源:互聯網
上載者:User

       接著上節的來,我們在上節說了非強制中斷和tasklet,那這最後就是工作隊列了哦..

       工作隊列和前面討論的其他形式都不相同,它可以把工作推後,交由一個核心線程去執行----該工作總是會在進程上下文執行。這樣,通過工作隊列執行代碼能佔盡進程內容相關的所有優勢,最重要的就是工作隊列允許重新調度甚至是睡眠。相比較前邊兩個,這個選擇起來就很容易了。我說過,前邊兩個是不允許休眠的,這個是允許休眠的,這就很明白了是不?這意味著在你需要獲得大量記憶體的時候,在你需要擷取訊號量時,在你需要執行阻塞式的I/O操作時,它都會非常有用(先說話, 這個不是我說的,是書上這麼說的哦)。

       工作隊列子系統是一個用於建立核心線程的介面,通過它建立的進程負責執行由核心其他部分排到隊列裡的任務。它建立的這些核心線程被稱作工作者線程(worker threads).工作隊列可以讓你的驅動程式建立一個專門的工作者線程來處理需要推後的工作。不過,工作隊列子系統提供了一個預設的工作者線程來處理這些工作。因此,工作隊列最基本的表現形式就轉變成一個把需要推後執行的任務交給特定的通用線程這樣一種介面。預設的背景工作執行緒叫做event/n.每個處理器對應一個線程,這裡的n代表了處理器編號。除非一個驅動程式或者子系統必須建立一個屬於自己的核心線程,否則最好還是使用預設線程。

       1.工作這線程結構用下面的結構表示:

struct workqueue_struct{struct cpu_workqueue_struct cpu_wq[NR_CPUS];}

       結構中數組的每一項對應系統的一個CPU.接下來,在看看在kernel/workqueue.c中的核心資料結構cpu_workqueue_struct:

struct cpu_workqueue_struct{spinlock_t lock;atomic_t nr_queued;struct list_head worklist;wait_queue_head_t more_work;wait_queue_head_t work_done;struct workqueue_struct *wq;task_t *thread;struct completion exti;}

       2.表示工作的資料結構:所有的工作者線程都是用普通的核心線程來實現的,它們都要執行worker_thread()函數。在它初始化完以後,這個函數執行一個死迴圈執行一個迴圈並開始休眠,當有操作被插入到隊列的時候,線程就會被喚醒,以便執行這些操作。當沒有剩餘的時候,它又會繼續休眠。工作有work_struct(linux/workqueue)結構表示:

struct work_struct{unsigned long pending;struct list_head entry;       //串連所有工作的鏈表void (*func)(void *);         //處理函數void *data;      //傳遞給處理函數的參數void *wq_data;struct timer_list timer;      //y延遲工作隊列所用到的定時器}

       當一個背景工作執行緒被喚醒時,它會執行它的鏈表上的所有工作。工作一旦執行完畢,它就將相應的work_struct對象從鏈表上移去,當鏈表不再有對象時,它就繼續休眠。woker_thread函數的核心流程如下:

for(;;){set_task_state(current,TASK_INTERRUPTIBLE);add_wait_queue(&cwq->more_work,&wait);if(list_empty(&cwq->worklist))schedule();elseset_task_state(current,TASK_RUNNING);remove_wait_queue(&cwq->more_work,&wait);if(!list_empty(&cwq->worklist))run_workqueue(cwq);}

       分析一下上面的代碼。首先線程將自己設定為休眠狀態並把自己加入等待隊列。如果工作對列是空的,線程調用schedule()函數進入睡眠狀態。如果鏈表有對象,線程就將自己設為運行態,脫離等待隊列。然後,再次調用run_workqueue()執行推後的工作。好了,接下來,問題就糾結在run_workqueue(),它完成實際推後到此的工作:

while(!list_empty(&cwq->worklist)){struct work_struct *work = list_entry(cwq->worklist.next,struct work_struct,entry);void (*f)(void *) = work->func;void *data = work->data;list_del_init(cwq->worklist.next);clear_bit(0,&work->pending);f(data);}

       該函數迴圈遍曆鏈表上每個待處理的工作,執行鏈表上每個結點上的work_struct的func成員函數:

1.當鏈表不為空白時,選取下一個節點對象。
2.擷取我們希望執行的函數func及其參數data。
3.把該結點從鏈表上接下來,將待處理標誌位pending清0。
4.調用函數。
5.重複執行。

       老師說的好:光說不練,不是好漢。現在我們繼續來看看怎麼用吧:

       1.首先,實際建立一些需要推後完成的工作,可以在編譯時間靜態地建立該資料結構:

DECLARE_WORK(name,void (*func)(void *),void *data);

當然了,如果願意,我們當然可以在運行時通過指標動態建立一個工作:

INIT_WORK(struct work_struct *work, void (*func)(void *),void *data);

       2.工作隊列處理函數,會由一個工作者線程執行,因此,函數會運行在進程上下文中,預設情況下,允許相應中斷,並且不持有鎖。如果需要,函數可以睡眠。需要注意的是,儘管處理函數運行在進程上下文中,但它不能訪問使用者空間,因為核心線程在使用者空間沒有相應的記憶體映射。函數原型如下:

void 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是新核心線程的名字。這樣就會建立所有的工作者線程(系統中的每個處理器都有一個)並且做好所有開始處理工作之前的準備工作。在建立之後,就調用下面的函數吧:

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()還需要檢查所有現存的待處理的下半部並執行它們。

       好了,這一次講完了,畫了兩次,我們在這兩次中提到了一些同時發生的問題,這時可能存在資料共用互斥訪問的問題,這個就是核心同步方面的事情了,我們後面再慢慢說這個事。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.