Linux workqueue工做原理 【轉】

 

轉自:http://blog.chinaunix.net/uid-21977330-id-3754719.htmlcss

轉自:http://bgutech.blog.163.com/blog/static/18261124320116181119889/
1. 什麼是workqueue
       Linux中的Workqueue機制就是爲了簡化內核線程的建立。經過調用workqueue的接口就能建立內核線程。而且能夠根據當前系統CPU的個 數建立線程的數量,使得線程處理的事務可以並行化。workqueue是內核中實現簡單而有效的機制,他顯然簡化了內核daemon的建立,方便了用戶的 編程.html

      工做隊列(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_LOCKDEP
    struct 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 其結構體就是:函數

truct cpu_workqueue_struct {
 spinlock_t lock;/*由於工做者線程須要頻繁的處理鏈接到其上的工做,因此須要枷鎖保護*/
 struct list_head worklist;
 wait_queue_head_t more_work;
 struct work_struct *current_work; /*當前的work*/
 struct workqueue_struct *wq;   /*所屬的workqueue*/
 struct task_struct *thread; /*任務的上下文*/
} ____cacheline_aligned;

       在該結構主要維護了一個任務隊列,以及內核線程須要睡眠的等待隊列,另外還維護了一個任務上下文,即task_struct。
       三者之間的關係以下:ui

 

3. 建立工做
3.1 建立工做queue
a. create_singlethread_workqueue(name)
        該函數的實現機制以下圖所示,函數返回一個類型爲struct workqueue_struct的指針變量,該指針變量所指向的內存地址在函數內部調用kzalloc動態生成。因此driver在再也不使用該work queue的狀況下調用:atom

        void destroy_workqueue(struct workqueue_struct *wq)來釋放此處的內存地址。.net

 

        圖中的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. 示例


  1. //聲明
  2. static struct workqueue_struct *mdp_pipe_ctrl_wq; /* mdp mdp pipe ctrl wq */
  3. static struct delayed_work mdp_pipe_ctrl_worker;
  4. mdp_pipe_ctrl_wq = create_singlethread_workqueue("mdp_pipe_ctrl_wq");//建立工做隊列
  5. INIT_DELAYED_WORK(&mdp_pipe_ctrl_worker,mdp_pipe_ctrl_workqueue_handler);//delayed_work與task_func綁定。
  6. //處理函數
  7. static void mdp_pipe_ctrl_workqueue_handler(struct work_struct *work)
  8. {
  9.  mdp_pipe_ctrl(MDP_MASTER_BLOCK, MDP_BLOCK_POWER_OFF, FALSE);
  10. }
  11. //開始調用工做隊列,delay時間到了就執行處理函數。
  12. unsigned long mdp_timer_duration = (HZ/20); /* 50 msecond */
  13. /* send workqueue to turn off mdp power */
  14. queue_delayed_work(mdp_pipe_ctrl_wq,&mdp_pipe_ctrl_worker, mdp_timer_duration);
  15.  
  16. /* cancel pipe ctrl worker */
  17. cancel_delayed_work(&mdp_pipe_ctrl_worker);
  18. /* for workder can't be cancelled... */
  19. flush_workqueue(mdp_pipe_ctrl_wq);
  20. /* for workder can't be cancelled... */
  21. flush_workqueue(mdp_pipe_ctrl_wq);

 

在driver 程序中許多不少狀況須要設置延後執行的,這樣工做隊列就很好幫助咱們實現。

相關文章
相關標籤/搜索