Linux設備驅動之workqueue----中斷底半部機制

文章爲本人學習筆記和總結,如有錯誤,請多多指教;

引言:

linux實現中斷底半部的機制主要有tasklet、工作隊列、軟中斷和線程化;

本文主要介紹下工作隊列---workqueue

1、工作隊列(workqueue)簡介

Linux中的Workqueue機制就是爲了簡化內核線程的創建。通過調用workqueue的接口就能創建內核線程。並且可以根據當前系統CPU的個數創建線程的數量,使得線程處理的事務能夠並行化。workqueue是內核中實現簡單而有效的機制,他顯然簡化了內核daemon的創建,方便了用戶的編程.

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

由於每一個工作隊列都有一個或者多個專用的進程(內核線程),這些進程運行提交到該隊列的函數;

從表面看工作隊列(workqueue)類似於tasklet,它們都允許內核代碼請求某個函數在將來的時間被調用。但兩者有很大差別:

  • tasklet在軟中斷上下文中運行,因此所有的tasklet代碼必須是原子的;而工作隊列函數是在特殊的內核進程上下文中運行,因此具有更好的靈活性,如工作隊列可以調用和休眠
  • tasklet始終運行在被初始提交的同一CPU上,但這只是工作隊列的默認方式
  • 內核代碼可以請求延時給定的時間間隔後執行工作隊列函數

兩者關鍵區別:tasklet會在很短時間段內執行,並且是原子模式的,而工作隊列函數具有更長延時並且不必原子化;

2、工作隊列(workqueue)使用

  •  工作(work)數據結構:
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  
}; 
//工作隊列執行函數的原型:
void (*work_func_t)(struct work_struct *work);
//該函數會由一個工作者線程執行,因此其在進程上下文中,可以睡眠也可以中斷。但只能在內核中運行,無法訪問用戶空間。
  • 工作將以隊列形式組織成工作隊列(workqueue),其數據結構如下:
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;
}; 
  • 多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;

綜上所述:工作有struct work_struct的類型,工作隊列有struct workqueue_struct的類型,該結構定義在<linux/workqueue.h>中。在使用前,我們必須顯示的創建一個工作隊列,介紹如下:

  • 創建工作隊列workqueue

在使用工作隊列之前,首先我們需要創建工作隊列,可以使用下面兩個函數之一:

struct workqueue_struct *create_workqueue(const char *name);
struct workqueue_struct *create_singlethread_workqueue(const char *name); 

兩個函數的區別:

使用create_workqueue()函數,內核則會在系統中的每一個CPU上爲該工作隊列創建專用的線程;這種情況下,可能會對系統性能造成一定影響。

而如果單個線程能夠滿足要求,則建議使用create_singlethread_workqueue()函數創建工作隊列。

  • 提交工作work

1)創建好工作隊列後,那麼如何提交工作到一個工作隊列,首先需要填充work_struct結構體,可以有如下方法:

DECLARE_WORK(name, void (*func)(void *), void *data)
INIT_WORK(struct work_struct *work, void (*func)(void *), void *data)
PREPARE_WORK(struct work_struct *work, void (*func)(void *), void *data)

區別如下:

DECLARE_WORK()是一個宏定義,並且該宏定義是在編譯時完成的,即靜態創建方法,其中name爲結構名稱,func是工作隊列要調度的函數,data是傳遞給該函數的參數;

如果要在運行時構造work_struct結構,應該使用後兩個宏。INIT_WORK完成更加徹底的初始化工作,因此在首次使用該結構時,建議使用這個宏。而PREPARE_WORK完成幾乎相同的工作,但是它不會初始化鏈接work_struct結構的工作隊列的指針;因此如果工作work_struct已經被提交到工作隊列中,而只是需要修改該結構,則應該使用PREPARE_WORK。

2)然後要將工作提交到工作隊列,則使用下面兩個函數之一:

int queue_work(struct workqueue_struct *queue, struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *queue, struct work_struct *work,
                unsigned long delat);

它們都會將work添加到指定的workqueue。但是如果使用queue_delayed_work,則實際的工作至少會在經過指定的jiffies(由delay指定)之後纔會被執行;如果工作被成功添加到隊列,則上述函數返回值爲1,。返回值爲非零時,則意味着給定的work_struct結構已經等待在該隊列中,從而不能添加兩次。

3、工作隊列調度schedule

使用如下函數調度工作隊列中的工作,使用如下函數之一:

int schedule_work(struct work_struct *work);
int schedule_delayed_work(struct work_struct *work, unsigned long delay);

兩者區別:如果要馬上調度運行work,則使用schedule_work()函數,即一旦其所在的處理器上的工作者線程被喚醒,它就會被執行;有時候並不希望工作馬上就被執行,而是希望它經過一段延遲以後再執行,則使用schedule_delayed_work()函數。

工作隊列是沒有優先級的,基本按照FIFO的方式進行處理;

4、工作隊列取消

如果要取消某個掛起的工作隊列入口項,可調用:

int cancel_delayed_work(struct work_struct *work);
void flush_workqueue(struct workqueue_struct *queue);

在結束對工作隊列的使用後,可調用下面函數釋放相關資源:

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