文章爲本人學習筆記和總結,如有錯誤,請多多指教;
引言:
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);