工作隊列 工作線程 工作(work_struct)

工作隊列(work queue)Linux kernel中將工作推後執行的一種機制。這種機制和BHTasklets不同之處在於工作隊列是把推後的工作交由一個內核線程去執行,因此工作隊列的優勢就在於它允許重新調度甚至睡眠。

workqueue,中文稱其爲工作隊列,是一個用於創建內核線程的接口,通過它創建的內核線程來執行內核其他模塊排列到隊列裏的工作,創建的內核線程被稱爲工作者線程。要理解工作隊列的實現,重點在於理解相關的三個數據結構的含義及關係


工作隊列是2.6內核開始引入的機制,在2.6.20之後,工作隊列的數據結構發生了一些變化,因此本文分成兩個部分對2.6.20之前和之後的版本分別做介紹。


I2.6.0~2.6.19

數據結構:
1
2
3
4
5
6
7
8
structwork_struct {
    unsignedlong pending;
    structlist_head entry;
    void(*func)(void*);
    void*data;
    void*wq_data;
    structtimer_list timer;
};

pending是用來記錄工作是否已經掛在隊列上;

entry是循環鏈表結構;

func作爲函數指針,由用戶實現;

data用來存儲用戶的私人數據,此數據即是func的參數;

wq_data一般用來指向工作者線程(工作者線程參考下文);

timer是推後執行的定時器。

work_struct的這些變量裏,funcdata是用戶使用的,其他是內部變量,我們可以不用太過關心。


1. 表示工作隊列類型的數據結構:struct workqueue_struct

  1. struct workqueue_struct {
  2.     struct cpu_workqueue_struct *cpu_wq;   /*工作者線程數組*/
  3.     struct list_head list; /*連接工作隊列類型的鏈表*/
  4.     const char *name;        /*工作者線程的名稱*/          
  5.     int singlethread;         /*是否創建新的工作者線程,0表示採用默認的工作者線程event/n*/
  6.     int freezeable;/* Freeze threads during suspend*/
  7.     int rt;
  8. #ifdef CONFIG_LOCKDEP
  9.     struct lockdep_map lockdep_map;
  10. #endif
  11. };

內核中默認的工作隊列爲:

  1. static struct workqueue_struct *keventd_wq __read_mostly;

其對應的工作者線程爲:event/n    其中,n代表當前cpu中processor的個數。

2. 表示工作者線程的數據結構:struct cpu_workqueue_struct

  1. struct 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; /*當前工作線程需要處理的工作*/

  6.     struct workqueue_struct *wq;   /*該工作者線程屬於那種類型的工作者隊列*/
  7.     struct task_struct *thread;    /*指向工作者線程的任務結構體*/
  8. } ____cacheline_aligned;


workqueue的執行非常簡單,即在每次運行工作者線程的時候,去遍歷工作者線程對應的工作鏈表上的工作,逐一進行處理即可,從這裏我們也可以猜到,工作隊列是沒有優先級的,基本按照FIFO的方式進行處理。


API

1
2
3
4
5
INIT_WORK(_work, _func, _data);
int schedule_work(struct work_struct *work);
int schedule_delayed_work(struct work_struct *work, unsigned long delay);
void flush_scheduled_work(void);
int cancel_delayed_work(struct work_struct *work);

1、初始化指定工作,目的是把用戶指定的函數_func_func需要的參數_data賦給work_structfuncdata變量。

2、對工作進行調度,即把給定工作的處理函數提交給缺省的工作隊列和工作者線程。工作者線程本質上是一個普通的內核線程,在默認情況下,每個CPU均有一個類型爲“events”的工作者線程,當調用schedule_work時,這個工作者線程會被喚醒去執行工作鏈表上的所有工作。

3、延遲執行工作,與schedule_work類似。

4、刷新缺省工作隊列。此函數會一直等待,直到隊列中的所有工作都被執行。

5、flush_scheduled_work並不取消任何延遲執行的工作,因此,如果要取消延遲工作,應該調用cancel_delayed_work

 

以上均是採用缺省工作者線程來實現工作隊列,其優點是簡單易用,缺點是如果缺省工作隊列負載太重,執行效率會很低,這就需要我們創建自己的工作者線程和工作隊列。

API

1
2
3
4
5
structworkqueue_struct *create_workqueue(constchar *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);
void flush_workqueue(struct workqueue_struct *wq);
void destroy_workqueue(struct workqueue_struct *wq);

1、創建新的工作隊列和相應的工作者線程name用於該內核線程的命名。

2、類似於schedule_work,區別在於queue_work把給定工作提交給創建的工作隊列wq而不是缺省隊列。

3、延遲執行工作。

4、刷新指定工作隊列。

5、釋放創建的工作隊列。

 

下面一段代碼可以看作一個簡單的實作:

1
2
3
4
5
6
7
8
9
10
11
12
13
void my_func(void *data)
{
    char*name = (char*)data;
    printk(KERN_INFO “Hello world, my name is %s!\n”, name);
}
 
structworkqueue_struct *my_wq = create_workqueue(“my wq”);
structwork_struct my_work;
 
INIT_WORK(&my_work, my_func, “Jack”);
queue_work(my_wq, &my_work);
 
destroy_workqueue(my_wq);

 


II2.6.20~2.6.??


2.6.20起,工作隊列的數據結構發生了一些變化,使用時不能沿用舊的方法。

 

數據結構:

1
2
3
4
5
6
7
typedefvoid (*work_func_t)(structwork_struct *work);
 
structwork_struct {
    atomic_long_t data;
    structlist_head entry;
    work_func_t func;
};

2.6.19之前的版本相比,work_struct瘦身不少。粗粗一看,entry和之前的版本相同,funcdata發生了變化,另外並無其他的變量。

entry我們不去過問,這個和以前的版本完全相同。data的類型是atomic_long_t,這個類型從字面上看可以知道是一個原子類型。第一次看到這個變量時,很容易誤認爲和以前的data是同樣的用法,只不過類型變了而已,其實不然,這裏的data是之前版本的pendingwq_data的複合體,起到了以前的pendingwq_data的作用。

func的參數是一個work_struct指針,指向的數據就是定義funcwork_struct

看到這裏,會有兩個疑問,第一,如何把用戶的數據作爲參數傳遞給func呢?以前有void *data來作爲參數,現在好像完全沒有辦法做到;第二,如何實現延遲工作?目前版本的work_struct並沒有定義timer

 

解決第一個問題,需要換一種思路。2.6.20版本之後使用工作隊列需要把work_struct定義在用戶的數據結構中,然後通過container_of來得到用戶數據。具體用法可以參考稍後的實作。

 

對於第二個問題,新的工作隊列把timer拿掉的用意是使得work_struct更加單純。首先回憶一下之前版本,只有在需要延遲執行工作時纔會用到timer,普通情況下timer是沒有意義的,所以之前的做法在一定程度上有些浪費資源。所以新版本中,將timerwork_struct中拿掉,然後又定義了一個新的結構delayed_work用於處理延遲執行:

1
2
3
4
structdelayed_work {
    structwork_struct work;
    structtimer_list timer;
};

 

下面把API羅列一下,每個函數的解釋可參考之前版本的介紹或者之後的實作:

1
2
3
4
5
6
7
8
9
10
11
INIT_WORK(structwork_struct *work, work_func_t func);
INIT_DELAYED_WORK(structdelayed_work *work, work_func_t func);
int schedule_work(struct work_struct *work);
int schedule_delayed_work(struct delayed_work *work, unsigned long delay);
structworkqueue_struct *create_workqueue(constchar *name);
int queue_work(struct workqueue_struct *wq, struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *work, unsigned long delay);
void flush_scheduled_work(void);
void flush_workqueue(struct workqueue_struct *wq);
int cancel_delayed_work(struct delayed_work *work);
voiddestroy_workqueue(structworkqueue_struct *wq);

其中,1、2、4、7和以前略有區別,其他用法完全一樣。

 

實作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
structmy_struct_t {
    char*name;
    structwork_struct my_work;
};
 
void my_func(struct work_struct *work)
{
    structmy_struct_t *my_name = container_of(work, structmy_struct_t, my_work);
    printk(KERN_INFO “Hello world, my name is %s!\n”, my_name->name);
}
 
structworkqueue_struct *my_wq = create_workqueue(“my wq”);
structmy_struct_t my_name;
 
my_name.name = “Jack”;
 
INIT_WORK(&(my_name.my_work), my_func);
queue_work(my_wq, &(my_name.my_work));
 
destroy_workqueue(my_wq);

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章