論linux內核工作隊列

說到工作隊列,相信接觸linux內核驅動的同學肯定看到過,但是爲什麼要引入工作隊列呢?工作隊列什麼時候引入的?
它的作用是什麼?我們應該怎麼使用它等等問題,一定困惑了不少剛接觸驅動的新人,當然也困惑了我很長一段時間
今天就我個人的學習經歷以及查找網上資源進行一個小結,本文僅代表個人愚見,如果不足之處還請指正和交流溝通,下面就進入正文!
工作隊列的引入是爲了把工作推後執行,工作推後執行之前已經有了軟中斷和tasklet這兩種下半部機制爲什麼還要進入工作隊列,
其實tasklet就是軟中斷的一個補充添加了一定的機制,軟中斷不能被自己打斷,能被硬中斷打斷,可以併發運行於多個cpu上,所以軟中斷必須設計爲可重入函數
那麼其數據結構就需要自旋鎖保護防止被修改爲不期望的數據。基於軟中斷必須使用可重入函數,帶來編程的複雜度,
於是就出現了tasklet補充這個不足,其實軟中斷類型默認11中,而tasklet就是其中的HI_SOFTIRQ和TASKLET_SOFTIRQ兩種相當於
是實現也是軟中斷機制一種特定的tasklet只能在一個cpu上運行,不同的tasklet可以運行在不同的cpu上;軟中斷是靜態分配的,在內核編譯好後就不能改變
tasklet就靈活許多。

其實相比於軟中斷和tasklet其優勢在於交給一個內核線程去執行,也就是說這個下半部允許在進程上下文中執行,尤其是可以重新調度甚至是睡眠
那麼,什麼情況下使用工作隊列,什麼情況下使用tasklet。如果推後執行的任務需要睡眠,那麼就選擇工作隊列。
如果推後執行的任務不需要睡眠,那麼就選擇tasklet。另外,如果需要用一個可以重新調度的實體執行你的下半部處理,
也應該使用工作隊列。它是唯一能在進程上下文運行的下半部實現的機制,也只有它纔可以睡眠。
這意味着在需要獲得大量的內存時、在需要獲取信號量時,在需要執行阻塞式的I/O操作時,它都會非常有用。
如果不需要用一個內核線程來推後執行工作,那麼就考慮使用 tasklet。

前面描述了關於工作隊列的引入帶來的優勢和作用,下面就來描述一下工作隊列的用法
工作隊列是在內核2.6版本引入的機制,在2.6.20之後做了些許改動,主要是數據結構上和函數API上的改動,具體看下面
在2.6.20之前的數據結構
struct work_struct {
unsigned long pending;
struct list_head entry;
void (func)(void );
void *data;
void *wq_data;
struct timer_list timer;
};
結構成員說明
pending是用來記錄工作是否已經掛在隊列上;
entry是循環鏈表結構;
func作爲函數指針,由用戶實現;
data用來存儲用戶的私人數據,此數據即是func的參數;
wq_data一般用來指向工作者線程(工作者線程參考下文);
timer是推後執行的定時器。
work_struct的這些變量裏,func和data是用戶使用的,其他是內部變量,我們可以不用太關心。
API:
1、INIT_WORK(_work, _func, _data);
2、int schedule_work(struct work_struct *work);
3、int schedule_delayed_work(struct work_struct *work, unsigned long delay);
4、void flush_scheduled_work(void);
5、int cancel_delayed_work(struct work_struct *work);
1、初始化指定工作,目的是把用戶指定的函數_func及_func需要的參數_data賦給work_struct的func及data變量。
2、對工作進行調度,即把給定工作的處理函數提交給缺省的工作隊列和工作者線程。工作者線程本質上是一個普通的內核線程,在默認情況下,每個CPU均有一個類型爲“events”的工作者線程,當調用schedule_work時,這個工作者線程會被喚醒去執行工作鏈表上的所有工作。
3、延遲執行工作,與schedule_work類似。
4、刷新缺省工作隊列。此函數會一直等待,直到隊列中的所有工作都被執行。
5、flush_scheduled_work並不取消任何延遲執行的工作,因此,如果要取消延遲工作,應該調用cancel_delayed_work。
以上均是採用缺省工作者線程來實現工作隊列,其優點是簡單易用,缺點是如果缺省工作隊列負載太重,執行效率會很低,這就需要我們創建自己的工作者線程和工作隊列。

API:
struct workqueue_struct *create_workqueue(const char *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、釋放創建的工作隊列。

2.6.20之後數據結構
typedef void (*work_func_t)(struct work_struct *work);
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
};
相比於之前的版本數據結構entry沒有變,func和data變了,而其它變量沒有了
data的類型是atomic_long_t,可以看出是原子類型,與之前的用法有所改變,現在是之前版本pending和wq_data的複合體;
func的參數是一個指向work_struct的指針,指向的數據就是定義func的work_struct。
所以可能就有疑問:用戶數據如何傳輸給函數和如何做到延時執行?之前版本是void *data傳參和timer表示延時時間

關於延時重新定義了一個結構體
struct delayed_work {
struct work_struct work;
struct timer_list timer;
};
這麼做的目的是只有在需要延時的工作纔會有到timer,之前的顯然有點浪費,而且普通情況下timer並沒有意義。
API
初始化隊列動態創建
INIT_WORK(struct work_struct *work, work_func_t func);//定義正常執行的工作項
INIT_DELAYED_WORK(struct delayed_work *work, work_func_t func);//定義延後執行的工作項

靜態創建
DECLARE_WORK(name,function); //定義正常執行的工作項
DECLARE_DELAYED_WORK(name,function);//定義延後執行的工作項

創建工作隊列
struct workqueue_struct *create_singlethread_workqueue(const char *name);
struct workqueue_struct *create_workqueue(const char *name);
相比於create_singlethread_workqueue,create_workqueue同樣會分配一個wq的工作隊列,但是不同之處在於,對於多cpu系統而言,
對每一個cpu,都會爲之創建一個per_CPU的cwq結構,對應每一個cwq,都會生成一個新的work_thread進程,但是當用queue_work向cwq上
提交work節點時,是哪個cpu調用該函數,便向該cpu對應的cwq上的worklist上增加work節點
int schedule_work(struct work_struct *work);
work馬上會被調用,一旦其所在的處理器工作線程被喚醒就會被執行
int schedule_delayed_work(struct delayed_work *work, unsigned long delay);
work指向的工作在delay指定的節拍之後纔會被執行
struct workqueue_struct *create_workqueue(const char *name);
int queue_work(struct workqueue_struct *wq, struct work_struct *work);
把工作交給指定的工作隊列wq,而不是缺省的工作隊列
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);刪除工作
void destroy_workqueue(struct workqueue_struct *wq);釋放創建的工作隊列

demo示例1
static init_events(struct work_struct *work);
struct workqueue_struct *wq;
靜態創建工作
static DECLARE_WORK(init_work, init_events);
在內核模塊驅動執行prob函數中
創建工作隊列
wq = create_singlethread_workqueue(“init_work”);
if (wq == NULL) {
printk(“create wq fail!\n”);
return -ENOMEM;
}

然後可以使用進行調度隊列
queue_work(wq, &init_work);

demo示例2
動態創建工作
struct workqueue_struct *wq;
struct work_struct init_work;

在內核模塊驅動執行prob函數中
INIT_WORK(&init_work, init_events);
創建工作隊列
wq = create_singlethread_workqueue(“init_work”);
if (wq == NULL) {
printk(“create wq fail!\n”);
return -ENOMEM;
}

queue_work(wq, &init_work);

示例3
static struct delayed_work work;
static struct workqueue_struct * workqueue = NULL;
創建延時工作達到輪詢的效果
INIT_DELAYED_WORK(&work, init_events);
workqueue = create_workqueue(“poll_check”);
調度延遲工作隊列(輪詢調度)
queue_delayed_work(gtp_esd_check_workqueue, &work,delay);
cancel_delayed_work_sync(&work);等待直到刪除(不能用於中斷上下文)
destroy_workqueue(wq);刪除創建的工作隊列

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