linux驅動---等待隊列、工做隊列、Tasklets【轉】

轉自:https://blog.csdn.net/eZiMu/article/details/54851148linux

版權聲明:本文爲博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接和本聲明。
本文連接:https://blog.csdn.net/eZiMu/article/details/54851148
概述:
等待隊列、工做隊列、Tasklet都是linux驅動很重要的API,下面主要從用法上來說述如何使用API.安全

應用場景:
等待隊列(waitqueue)
linux驅動中,阻塞通常就是用等待隊列來實現,將進程中止在此處並睡眠下,直到條件知足時,纔可經過此處,繼續運行。在睡眠等待期間,wake up時,喚起來檢查條件,條件知足解除阻塞,不知足繼續睡下去。函數

工做隊列(workqueue)
工做隊列,將一個work提交到workqueue上,而這個workqueue是掛到一個特殊內核進程上,當這個特殊內核進程被調度時,會從workqueue上取出work來執行。固然這裏的work是與函數聯繫起來的。這個過程表現爲,此刻先接下work,但不馬上執行這個work,等有時間再執行,而這個時間是不肯定的。
工做隊列運行在進程上下文,能夠睡眠。atom

Tasklet
Tasklet,一樣,也是先接下任務,但不馬上作任務,與work很相似。tasklet運行在軟中斷上下文。.net

軟中斷:有這樣三句話理解」硬中斷是外部設備對CPU的中斷」,」軟中斷一般是硬中斷服務程序對內核的中斷」,」信號則是由內核(或其餘進程)對某個進程的中斷」線程

這三句話,是比較籠統的理解,如今回到linux具體來理解:指針

軟中斷觸發時機:
(1)中斷上下文觸發(在中斷服務程序中),在中斷服務程序退出後,軟中斷會獲得立馬處理。
(2)非中斷上下文(也能夠理解進程上下文),經過喚醒守護進程ksoftirqd,只有當守護進程獲得調度後,軟中斷纔會獲得處理。
不論是中斷上下文,仍是非中斷上下文,最終都是調用__do_softirq實現的軟中斷,在這個函數裏面是打開硬件中斷,關閉內核搶佔。這就是軟中斷上下文,即開硬件中斷,關閉搶佔。blog

tasklet是基於軟中斷實現的,用在中斷服務程序觸發tasklet,則就是中斷下半部分,也是用得最多的狀況。用在進程上下文觸發tasklet,則很相似workqueue,可是tasklet不能有睡眠(由於關閉搶佔的,不考慮硬件中斷,就是原子性的),也不適合作很是耗時的,若是是很是耗時的,儘可能交給workqueue(能夠在tasklet回調裏面用work,把更耗時,時間要求更不高的,交給workqueue)。接口

軟中斷詳細瞭解,可參考以下博文:
linux軟中斷機制分析
linux中斷底半部之 softirq 原理與代碼分析
linux軟中斷與硬中斷實現原理概述
硬中斷、軟中斷和信號隊列

等待隊列(waitqueue)
定義頭文件:
#include <linux/wait.h>
1
定義和初始化等待隊列頭(workqueue):
靜態的,用宏:
#define DECLARE_WAIT_QUEUE_HEAD(name) \
wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
1
2
動態的,也是用宏:

#define init_waitqueue_head(q) \
do { \
static struct lock_class_key __key; \
\
__init_waitqueue_head((q), #q, &__key); \
} while (0)
1
2
3
4
5
6
如:

wait_queue_head_t wq;
init_waitqueue_head(&wq);
1
2
阻塞接口:
都是些宏:
wait_event(wq, condition)
wait_event_timeout(wq, condition, timeout)
wait_event_interruptible(wq, condition)
wait_event_interruptible_timeout(wq, condition, timeout)
wait_event_hrtimeout(wq, condition, timeout)
wait_event_interruptible_hrtimeout(wq, condition, timeout)
wait_event_interruptible_exclusive(wq, condition)
wait_event_interruptible_locked(wq, condition)
wait_event_interruptible_locked_irq(wq, condition)
wait_event_interruptible_exclusive_locked(wq, condition)
wait_event_interruptible_exclusive_locked_irq(wq, condition)
wait_event_killable(wq, condition)
wait_event_lock_irq_cmd(wq, condition, lock, cmd)
wait_event_lock_irq(wq, condition, lock)
wait_event_interruptible_lock_irq_cmd(wq, condition, lock, cmd)
wait_event_interruptible_lock_irq(wq, condition, lock)
wait_event_interruptible_lock_irq_timeout(wq, condition, lock, timeout)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
接口版本比較多,各自都有本身合適的應用場合,可是經常使用的是前面四個。
其中wq是咱們定義的等待隊列頭,condition爲條件表達式,當wake up後,condition爲真時,喚醒阻塞的進程,爲假時,繼續睡眠。
wait_event:不可中斷的睡眠,條件一直不知足,會一直睡眠。
wait_event_timeout:不可中斷睡眠,當超過指定的timeout(單位是jiffies)時間,無論有沒有wake up,仍是條件沒知足,都要喚醒進程,此時返回的是0。在timeout時間內條件知足返回值爲timeout或者1;
wait_event_interruptible:可被信號中斷的睡眠,被信號打斷喚醒時,返回負值-ERESTARTSYS;wake up時,條件知足的,返回0。除了wait_event沒有返回值,其它的都有返回,有返回值的通常都要判斷返回值。以下例:

int flag = 0;
if(wait_event_interruptible(&wq,flag == 1))
return -ERESTARTSYS;
1
2
3
4
wait_event_interruptible_timeout:是wait_event_timeout和wait_event_interruptible_timeout的結合版本,有它們兩個的特色。

其餘的接口,用的很少,有興趣能夠本身看看。

解除阻塞接口(喚醒)
接口也是些宏:
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
#define wake_up_nr(x, nr) __wake_up(x, TASK_NORMAL, nr, NULL)
#define wake_up_all(x) __wake_up(x, TASK_NORMAL, 0, NULL)
#define wake_up_locked(x) __wake_up_locked((x), TASK_NORMAL, 1)
#define wake_up_all_locked(x) __wake_up_locked((x), TASK_NORMAL, 0)

#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
#define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
#define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
#define wake_up_interruptible_sync(x) __wake_up_sync((x), TASK_INTERRUPTIBLE, 1)
1
2
3
4
5
6
7
8
9
10
wake_up:一次只能喚醒掛在這個等待隊列頭上的一個進程
wake_up_nr:一次喚起nr個進程(等待在同一個wait_queue_head_t有不少個)
wake_up_all:一次喚起全部等待在同一個wait_queue_head_t上全部進程
wake_up_interruptible:對應wait_event_interruptible版本的wake up
wake_up_interruptible_sync:保證wake up的動做原子性,wake_up這個函數,頗有可能函數還沒執行完,就被喚起來進程給搶佔了,這個函數可以保證wak up動做完整的執行完成。
其餘的也是與對應阻塞接口對應的。

靈活的添加刪除等待隊列頭中的等待隊列:
這小節,能夠不看,對應用,不是很重要。
(1)定義:
靜態:
#define DECLARE_WAITQUEUE(name, tsk) \
wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
1
2
(2)動態:

wait_queue_t wa;
init_waitqueue_entry(&wa,&tsk);
1
2
tsk是進程結構體,通常是current(linux當前進程就是用這個獲取)。還能夠用下面的,設置自定義的等待隊列回調函數,上面的是linux默認的一個回調函數default_wake_function(),不過默認的用得最多:

wait_queue_t wa;
wa->private = &tsk;
int func(wait_queue_t *wait, unsigned mode, int flags, void *key)
{
//
}
init_waitqueue_func_entry(&wa,func);
1
2
3
4
5
6
7
(回調有什麼做用?)
用下面函數將等待隊列,加入到等待隊列頭(帶remove的是從工做隊列頭中刪除工做隊列):

extern void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
extern void add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait);
extern void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
1
2
3
上面的阻塞和解除阻塞接口,只能是對當前進程阻塞/解除阻塞,有了這幾個靈活的接口,咱們能夠單獨定義一個等待隊列,只要獲取進程task_struct指針,咱們能夠將任何進程加入到這個等待隊列,而後加入到等待隊列頭,咱們能將其它任何進程(不只僅是當前進程),掛起睡眠,固然喚醒時,若是用wake_up_all版本的話,也會一同喚起。這種狀況,阻塞不能用上面的接口了,咱們須要用下一節講述的接口(schedule()),解除阻塞能夠用wake_up,wake_up_interruptible等。

更高級靈活的阻塞:
阻塞當前進程的原理:用函數set_current_state()修改當前進程爲TASK_INTERRUPTIBLE(不可中斷睡眠)或TASK_UNINTERRUPTIBLE(可中斷睡眠)狀態,而後調用schedule()告訴內核從新調度,因爲當前進程狀態已經爲睡眠狀態,天然就不會被調度。schedule()簡單說就是告訴內核當前進程主動放棄CPU控制權。這樣來,就能夠說當前進程在此處睡眠,即阻塞在這裏。
在上一小節「靈活的添加刪等待隊列頭中的等待隊列」,將任意進程加入到waitqueue,而後相似用:

task_struct *tsk;
wait_queue_t wa;
//假設tsk已經指向某進程控制塊
p->state = TASK_INTERRUPTIBLE;//or TASK_UNINTERRUPTIBLE
init_waitqueue_entry(&wa,&tsk);
1
2
3
4
5
就能將任意進程掛起,固然,還須要將wa,掛到等待隊列頭,而後用wait_event(&wa),進程就會從就緒隊列中退出,進入到睡眠隊列,直到wake up時,被掛起的進程狀態被修改成TASK_RUNNING,纔會被再次調度。(主要是schedule()下面會說到)。

先看下wait_event實現:

#define __wait_event(wq, condition) \
do { \
DEFINE_WAIT(__wait); \
\
for (;;) { \
prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); \
if (condition) \
break; \
schedule(); \
} \
finish_wait(&wq, &__wait); \
} while (0)

#define wait_event(wq, condition) \
do { \
if (condition) \
break; \
__wait_event(wq, condition); \
} while (0)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
prepare_to_wait:
定義:void prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)
功能:將工做隊列wait加入到工做隊列頭q,並將當前進程設置爲state指定的狀態,通常是TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE狀態(在這函數裏有調用set_current_state)。
第一個參數:工做隊列頭
第二個參數:工做隊列
第三個參數:當前進程要設置的狀態

DEFINE_WAIT:定義一個工做隊列。
finish_wait:用了prepare_to_wait以後,當退出時,必定要用這個函數清空等待隊列。

從這個宏的實現,能夠看出睡眠進程過程,prepare_to_wait先修改進程到睡眠狀態,條件不知足,schedule()就放棄CPU控制權,睡眠,當wake up的時候,阻塞在wq(也能夠說阻塞在wait_event處)等待隊列頭上的進程,再次獲得運行,接着執行schedule()後面的代碼,這裏,顯然是個循環,prepare_to_wait再次設置當前進程爲睡眠狀態,而後判斷條件是否知足,知足就退出循環,finish_wait將當前進程恢復到TASK_RUNNING狀態,也就意味着阻塞解除。不知足,繼續睡下去。如此反覆等待條件成立。

明白這個過程,用prepare_to_wait和schedule()來實現更爲靈活的阻塞,就很簡單了,解除阻塞和前面的同樣用wake_up,wake_up_interruptible等。

wait_queue_t成員flage重要的標誌WQ_FLAG_EXCLUSIVE,表示:

當一個等待隊列入口有 WQ_FLAG_EXCLUSEVE 標誌置位, 它被添加到等待隊列的尾
部. 沒有這個標誌的入口項, 添加到開始.
當 wake_up 被在一個等待隊列上調用, 它在喚醒第一個有 WQ_FLAG_EXCLUSIVE 標
志的進程後中止.
wait_event默認老是將waitqueue加入開始,而wake_up時老是一個一個的從開始處喚醒,若是不斷有waitqueue加入,那麼最開始加入的,就一直得不到喚醒,有這個標誌,就避免了這種狀況。

prepare_to_wait_exclusive()就是加入了這個標誌的。

工做隊列:
頭文件:
#include <linux/workqueue.h>
1
建立workqueue:
#define create_workqueue(name) \
alloc_workqueue((name), WQ_MEM_RECLAIM, 1)

#define create_singlethread_workqueue(name) \
alloc_ordered_workqueue("%s", WQ_MEM_RECLAIM, name)
1
2
3
4
5
這兩個宏都會返回一個workqueue_struct結構體的指針,而且都會建立進程(「內核線程」)來執行加入到這個workqueue的work。
create_workqueue:多核CPU,這個宏,會在每一個CPU上建立一個專用線程。
create_singlethread_workqueue:單核仍是多核,都只在其中一個CPU上建立線程。

用法例子:

struct workqueue *wq,*ws;
wq = create_workqueue("wqname");
ws = create_singlethread_workqueue("wsname");
1
2
3
定義work:
(1)靜態(其實,將這個宏,放到局部變量裏面,也是個動態的):
#define DECLARE_WORK(n, f) \
struct work_struct n = __WORK_INITIALIZER(n, f)
1
2
用法例子:

void func(struct work_struct *work)
{

}
DECLARE_WORK(wo,func);
1
2
3
4
5
(2)動態定義:

#define INIT_WORK(_work, _func) \
do { \
__INIT_WORK((_work), (_func), 0); \
} while (0)
1
2
3
4
用法例子:

void func(struct work_struct *work)
{
}
struct work_struct wo;
INIT_WORK(&wo,func);
1
2
3
4
5
還用以下宏,用來修改work綁定的函數:

#define PREPARE_WORK(_work, _func) \
do { \
(_work)->func = (_func); \
} while (0)
1
2
3
4
如:

void func(struct work_struct *work){}
void funca(struct work_struct *work){}
struct work_struct wo;
INIT_WORK(&wo,func);
PREPARE_WORK(&wo,funca);
1
2
3
4
5
修改綁定的函數後,當下次調度到,funca函數被調度,再也不是func。

(3)將work加入到workqueue
有兩個函數:

bool queue_work(struct workqueue_struct *wq,struct work_struct *work);
bool queue_delayed_work(struct workqueue_struct *wq,
struct delayed_work *dwork,
unsigned long delay);
1
2
3
4
兩個函數的返回值:
返回0,表示work在這以前,已經在workqueue中了
返回非0,表示work成功加入到workqueue中了
queue_delayed_work表示不是立刻把work加入到workqueue中,而是延後delay(時間單位jiffies),再加入。注意它的work(dwork)要用宏(靜態)DECLARE_DELAYED_WORK來定義和初始化,動態的能夠用INIT_DELAYED_WORK,用法和沒有延後的差很少。

須要注意:當這個work被調度一次後,就從workqueue中取消了,若是還須要work被調度到(即work中的函數再被調用),須要從新加入到workqueue中,通常能夠直接在work綁定的函數,最後一行調用這個兩個函數再次加入。

(4)取消work
有兩個版本
queue_work對應的版本:

bool cancel_work_sync(struct work_struct *work);
1
注意:調用這個函數,必須確保work所在的workqueue沒被銷燬,調用這函數的進程會等待這個work執行完成(得不到執行,進程會阻塞等待),再取消這個work。這個函數返回後,work確定是被執行了。

queue_delayed_work對應的版本:

bool cancel_delayed_work(struct delayed_work *dwork);
bool cancel_delayed_work_sync(struct delayed_work *dwork);
1
2
cancel_delayed_work:返回後,work並不必定被取消,有可能還在運行。
cancel_delayed_work_sync:返回後,work確定已經被取消了。等到work被執行後,取消完成才返回。

銷燬workqueue
銷燬函數:

void destroy_workqueue(struct workqueue_struct *wq);
1
在銷燬前,最好調用flush_workqueue來確保在這workqueue上的work都處理完了:

void flush_workqueue(struct workqueue_struct *wq);
1
總結:工做隊列步驟,首先是建立workqueue和定義初始化work,而後將work加入到workqueue中。最後,不要時,銷燬workqueue。

共享工做隊列
共享隊列,就是系統建立了默認的workqueue,只須要定義初始化work,調用接口就完成。
兩個接口:

bool schedule_work(struct work_struct *work);
bool schedule_delayed_work(struct delayed_work *dwork,
unsigned long delay);
1
2
3
例子:

void func(struct work_struct *work)
{
}
struct work_struct wo;
INIT_WORK(&wo,func);
schedule_work(&wo);
1
2
3
4
5
6
取消仍是用:

bool cancel_work_sync(struct work_struct *work);
bool cancel_delayed_work(struct delayed_work *dwork);
bool cancel_delayed_work_sync(struct delayed_work *dwork);
1
2
3
對應版本接口,用對應版本接口取消。

取消後,通常須要調用下面接口,確保work完成,並取消了:

void flush_scheduled_work(void);
1
flush_scheduled_work能確保在系統默認建立的workqueue上全部的work都完成了。

Tasklet
頭文件:
#include <linux/interrupt.h>
1
定義和初始化:
(1)靜態:**
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};

#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
name:定義的tasklet_struct結構體變量
func:回調函數void (*func)(unsigned long);
data:私有數據能夠是具體一個整數,或者指針。沒有通常爲0。

DECLARE_TASKLET定義是直接能夠用tasklet_schedule()加入到調度的。
DECLARE_TASKLET_DISABLED定義的,用這個tasklet_schedule()也沒法調度到,須要使用tasklet_enable()使能,才能夠被調度運行。

(2)動態:

void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data);
1
2
用法:

struct tasklet_struct tl;
void func(unsigned long){}
tasklet_init(&tl,func,0);
1
2
3
函數接口:

void tasklet_schedule(struct tasklet_struct *t);
void tasklet_hi_schedule(struct tasklet_struct *t);
void tasklet_hi_schedule_first(struct tasklet_struct *t);
void tasklet_disable_nosync(struct tasklet_struct *t);
void tasklet_disable(struct tasklet_struct *t);
void tasklet_enable(struct tasklet_struct *t);
void tasklet_kill(struct tasklet_struct *t);
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data);
1
2
3
4
5
6
7
8
9
tasklet_schedule:將tasklet加入到調度鏈表裏面,tasklet就能獲得執行,每調用這個函數一次,tasklet只能執行一次,要再次執行須要從新調用這個函數。
tasklet_hi_schedule:比tasklet_schedule優先級更高,能夠獲得更快處理。
tasklet_hi_schedule_first:和tasklet_hi_schedule差很少,只是更安全。
tasklet_disable:禁止tasklet,即便tasklet_schedule已經把tasklet調度鏈表裏,也得不到執行,必需要用tasklet_enable使能才能夠。若是當前tasklet正在運行,tasklet_disable會等待執行完,而後禁止,返回。
tasklet_disable_nosync:和tasklet_disable同樣,若是當前tasklet在運行,這個函數不會等待完成就先返回,當tasklet完成退出後,再禁止。
tasklet_enable:使能tasklet,和tasklet_disable要成對使用。
tasklet_kill:設備關閉和模塊卸載的時候,調用來殺死tasklet。若是當前tasklet在運行,會等待完成後,再殺死。
tasklet_init:初始化tasklet。

tasklet步驟:定義初始化綁定函數,而後調用接口把tasklet加入到調度,在這個過程當中,可使能和禁止。————————————————版權聲明:本文爲CSDN博主「eZiMu」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接及本聲明。原文連接:https://blog.csdn.net/eZiMu/article/details/54851148

相關文章
相關標籤/搜索