在Linux內核中等待隊列有很多用途,可用於中斷處理、進程同步及定時。我們在這里只說,進程經常必須等待某些事件的發生。等待隊列實現了在事件上的條件等待: 希望等待特定事件的進程把自己放進合適的等待隊列,並放棄控制全。因此,等待隊列表示一組睡眠的進程,當某一條件為真時,由內核喚醒它們。
等待隊列由循環鏈表實現,其元素包括指向進程描述符的指針。每個等待隊列都有一個等待隊列頭(wait queue head),等待隊列頭是一個類型為wait_queue_head_t的數據結構
(1)定義等待隊列頭(相關內容可以在linux/include/wait.h中找到)
等待隊列頭結構體的定義:
struct __wait_queue_head {
spinlock_t lock; //自旋鎖變量,用於在對等待隊列頭
struct list_head task_list; // 指向等待隊列的list_head
};
typedef struct __wait_queue_head wait_queue_head_t;
使用等待隊列時首先需要定義一個wait_queue_head,這可以通過DECLARE_WAIT_QUEUE_HEAD宏來完成,這是靜態定義的方法。該宏會定義一個wait_queue_head,並且初始化結構中的鎖以及等待隊列。當然,動態初始化的方法也很簡單,初始化一下鎖及隊列就可以了。
#define DECLARE_WAIT_QUEUE_HEAD(name) \
wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
#define __WAIT_QUEUE_HEAD_INITIALIZER(name) { \
.lock = __SPIN_LOCK_UNLOCKED(name.lock), \
.task_list = { &(name).task_list, &(name).task_list } }
將lock賦為unlocked, 將等待隊列頭指向的等待隊列鏈表指向name,從而將等待隊列頭和等待隊列連起來;
一般在寫程序的時候將DECLARE_WAIT_QUEUE_HEAD分成兩步來完成:
聲明:
wait_queue_head_t wait_que;
初始化:
init_waitqueue_head( &wait_que);
Linux中等待隊列的實現思想如下圖所示,當一個任務需要在某個wait_queue_head上睡眠時,將自己的進程控制塊信息封裝到wait_queue中,然后掛載到wait_queue的鏈表中,執行調度睡眠。當某些事件發生后,另一個任務(進程)會喚醒wait_queue_head上的某個或者所有任務,喚醒工作也就是將等待隊列中的任務設置為可調度的狀態,並且從隊列中刪除。
(2)等待隊列中存放的是在執行設備操作時不能獲得資源而掛起的進程
定義等待對列:
struct __wait_queue {
unsigned int flags; //prepare_to_wait()里有對flags的操作,查看以得出其含義
#define WQ_FLAG_EXCLUSIVE 0x01 //一個常數,在prepare_to_wait()用於修改flags的值
void * private //通常指向當前任務控制塊
wait_queue_func_t func; //喚醒阻塞任務的函數 ,決定了喚醒的方式
struct list_head task_list; // 阻塞任務鏈表
};
typedef struct __wait_queue wait_queue_t;
//聲明一個等待隊列並初始化為name
#define DECLARE_WAITQUEUE(name, tsk) \
wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
#define __WAITQUEUE_INITIALIZER(name, tsk) { \
.private = tsk, \
.func = default_wake_function, \
.task_list = { NULL, NULL } }
//下列兩個函數用於對特定的成員進行賦值(當傳入不同類型的參數時);
static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)
{
q->flags = 0;
q->private = p; //私有數據指針
q->func = default_wake_function; //使用默認的喚醒函數
}
static inline void init_waitqueue_func_entry(wait_queue_t *q, wait_queue_func_t func)
{
q->flags = 0;
q->private = NULL;
q->func = func; // 自定義的喚醒函數
}
(3)對等待隊列進行操作
static inline int waitqueue_active(wait_queue_head_t *q)
{
return !list_empty(&q->task_list);
}
判斷等待對列頭是否為空,當一個進程訪問設備而得不到資源時就會被放入等待隊列頭指向的等待隊列中。
static inline void __add_wait_queue(wait_queue_head_t *head,\ wait_queue_t *new) /
{
list_add(&new->task_list, &head->task_list);
}
//增加一個等待隊列new到等待隊列頭head指向的等待隊列鏈表中;
static inline void __add_wait_queue_tail(wait_queue_head_t *head, wait_queue_t *new)
{
list_add_tail(&new->task_list, &head->task_list);
}
增加一個等待隊列到表尾
static inline void __remove_wait_queu (wait_queue_head_t *head, wait_queue_t *old)
{
list_del(&old->task_list);
}
//wq: 在等待事件的等待隊列,condition: 等待的條件
#define __wait_event(wq, condition) \
do { \
DEFINE_WAIT(__wait); \ //定義並初始化一個wait_queue_t結構
for (;;) { \
prepare_to_wait(&wq, &__wait,TASK_UNINTERRUPTIBLE); \
if (condition) \ //看wait_queue:wq要等的condition是否滿足
break; \
schedule(); \ //condition不成立,放棄cpu重新調度一個task
} \
finish_wait(&wq, &__wait); \
} while (0)
上面程序的執行過程:
1.用當前的進程描述塊(PCB)初始化一個wait_queue描述的等待任務。
2.在等待隊列鎖資源的保護下,將等待任務加入等待隊列。
3.判斷等待條件是否滿足,如果滿足,那么將等待任務從隊列中移出,退出函數。
4.如果條件不滿足,那么任務調度,將CPU資源交與其它任務。
5.當睡眠任務被喚醒之后,需要重復(2)、(3)步驟,如果確認條件滿足,退出等待事件函數。
我在一個程序中因為使用使用wait_event_interruptible()遇到了很大的麻煩,原因就是不知道condition在函數中具體起個什么作用,通過分析源碼終於搞清楚。(后面會有專門的文章來介紹那個問題。)
等待隊列編程接口:
wait_event(wq, condition)
這是一個宏,讓當前任務處於等待事件狀態。輸入參數如下:
@wq:等待隊列
@conditions:等待條件
wait_event_timeout(wq, condition, timeout)
功能與wait_event類似,多了一個超時機制。參數中多了一項超時時間。
wait_event_interruptible(wq, condition)
這是一個宏,與前兩個宏相比,該宏定義的等待能夠被消息喚醒。如果被消息喚醒,那么返回- ERESTARTSYS。輸入參數如下:
@wq:等待隊列
@condition:等待條件
@rt:返回值
wait_event_interruptible_timeout(wq, condition, timeout)
與上一個相比,多了超時機制
wake_up(x)
喚醒等待隊列中的一個任務
wake_up_interruptible(x)
用於喚醒wake_event_interruptible()睡眠的進程
wake_up_all(x)
喚醒等待隊列中的所有任務
Linux將進程狀態描述為如下五種:
TASK_RUNNING:可運行狀態。處於該狀態的進程可以被調度執行而成為當前進程。
TASK_INTERRUPTIBLE:可中斷的睡眠狀態。處於該狀態的進程在所需資源有效時被喚醒,也可以通過信號或定時中斷喚醒(因為有signal_pending()函數)。
TASK_UNINTERRUPTIBLE:不可中斷的睡眠狀態。處於該狀態的進程僅當所需資源有效時被喚醒。
TASK_ZOMBIE:僵屍狀態。表示進程結束且已釋放資源,但其task_struct仍未釋放。
TASK_STOPPED:暫停狀態。處於該狀態的進程通過其他進程的信號才能被喚醒。