1. 程式人生 > >工作對列與tasklet

工作對列與tasklet

轉載https://www.cnblogs.com/oceanding/p/7595738.html

linux工作佇列

轉載http://bgutech.blog.163.com/blog/static/18261124320116181119889/

1. 什麼是workqueue
       Linux中的Workqueue機制就是為了簡化核心執行緒的建立。通過呼叫workqueue的介面就能建立核心執行緒。並且可以根據當前系統CPU的個數建立執行緒的數量,使得執行緒處理的事務能夠並行化。workqueue是核心中實現簡單而有效的機制,他顯然簡化了核心daemon的建立,方便了使用者的程式設計.

      工作佇列(workqueue)是另外一種將工作推後執行的形式.工作佇列可以把工作推後,交由一個核心執行緒去執行,也就是說,這個下半部分可以在程序上下文中執行。最重要的就是工作佇列允許被重新排程甚至是睡眠。

2. 資料結構
     我們把推後執行的任務叫做工作(work),描述它的資料結構為work_struct:

 

  1. struct work_struct {  
  2.     atomic_long_t data;       /*工作處理函式func的引數*/  
  3. #define WORK_STRUCT_PENDING 0        /* T if work item pending execution */  
  4. #define WORK_STRUCT_STATIC 1        /* static initializer (debugobjects) */  
  5. #define WORK_STRUCT_FLAG_MASK (3UL)  
  6. #define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)  
  7.     struct list_head entry;        /*連線工作的指標*/  
  8.     work_func_t func;              /*工作處理函式*/  
  9. #ifdef CONFIG_LOCKDEP  
  10.     struct lockdep_map lockdep_map;  
  11. #endif  
  12. };  

 

      這些工作以佇列結構組織成工作佇列(workqueue),其資料結構為workqueue_struct:

[cpp] view plain copy

 

  1. struct workqueue_struct {  
  2.  struct cpu_workqueue_struct *cpu_wq;  
  3.  struct list_head list;  
  4.  const char *name;   /*workqueue name*/  
  5.  int singlethread;   /*是不是單執行緒 - 單執行緒我們首選第一個CPU -0表示採用預設的工作者執行緒event*/  
  6.  int freezeable;  /* Freeze threads during suspend */  
  7.  int rt;  
  8. };   


     如果是多執行緒,Linux根據當前系統CPU的個數建立cpu_workqueue_struct 其結構體就是:

[cpp] view plain copy

 

  1. truct cpu_workqueue_struct {  
  2.  spinlock_t lock;/*因為工作者執行緒需要頻繁的處理連線到其上的工作,所以需要枷鎖保護*/  
  3.  struct list_head worklist;  
  4.  wait_queue_head_t more_work;  
  5.  struct work_struct *current_work; /*當前的work*/  
  6.  struct workqueue_struct *wq;   /*所屬的workqueue*/  
  7.  struct task_struct *thread; /*任務的上下文*/  
  8. } ____cacheline_aligned;  

       在該結構主要維護了一個任務佇列,以及核心執行緒需要睡眠的等待佇列,另外還維護了一個任務上下文,即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. 示例

[cpp] view plain copy

 

  1. #include <linux/module.h>  
  2. #include <linux/init.h>  
  3. #include <linux/workqueue.h>  
  4.   
  5. static struct workqueue_struct *queue=NULL;  
  6. static struct work_struct   work;  
  7.   
  8. staticvoid work_handler(struct work_struct *data)  
  9. {  
  10.        printk(KERN_ALERT"work handler function.\n");  
  11. }  
  12.   
  13. static int __init test_init(void)  
  14. {  
  15.       queue=create_singlethread_workqueue("hello world");/*建立一個單執行緒的工作佇列*/  
  16.       if (!queue)  
  17.             goto err;  
  18.   
  19.        INIT_WORK(&work,work_handler);  
  20.        schedule_work(&work);  
  21.   
  22.       return0;  
  23. err:  
  24.       return-1;  
  25. }  
  26.   
  27. static   void __exit test_exit(void)  
  28. {  
  29.        destroy_workqueue(queue);  
  30. }  
  31. MODULE_LICENSE("GPL");  
  32. module_init(test_init);  
  33. module_exit(test_exit);  
 

序號

 

介面函式

 

說明

 

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。

 

 

TASKLET例子

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>

static struct  tasklet_struct my_tasklet;

static void tasklet_handler (unsigned long data)
{
        printk("tasklet_handler is running\n");
}

static int __init example_init(void)
{
        tasklet_init(&my_tasklet,tasklet_handler,0);
        tasklet_schedule(&my_tasklet);
        return 0;
}

static  void __exit example_exit(void)
{
        tasklet_kill(&my_tasklet);
        printk("example_exit is running\n");
}

module_init(example_init);
module_exit(example_exit);

MODULE_LICENSE("GPL");