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()是一個巨集定義,並且該巨集定義是在編譯時完成的;
如果要在執行時構造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結構已經等待在該佇列中,從而不能新增兩次。