在軟件開發中任務經常由於某種條件沒有得到滿足而不得不進入睡眠狀態,然後等待條件得到滿足的時候再繼續運行,進入運行狀態。這種需求需要等待隊列機制的支持。Linux中提供了等待隊列的機制,該機制在內核中應用很廣泛。
在Linux內核中使用等待隊列的過程很簡單,首先定義一個wait_queue_head,然後如果一個task想等待某種事件,那麼調用wait_event(等待隊列,事件)就可以了。
等待隊列應用廣泛,但是內核實現卻十分簡單。其涉及到兩個比較重要的數據結構:__wait_queue_head,該結構描述了等待隊列的鏈頭,其包含一個鏈表和一個原子鎖,結構定義如下:
struct __wait_queue_head {
spinlock_t lock; /* 保護等待隊列的原子鎖 */
struct list_head task_list; /* 等待隊列 */
};
__wait_queue,該結構是對一個等待任務的抽象。每個等待任務都會抽象成一個wait_queue,並且掛載到wait_queue_head上。該結構定義如下:
struct __wait_queue {
unsigned int flags;
void *private; /* 通常指向當前任務控制塊 */
/* 任務喚醒操作方法,該方法在內核中提供,通常為autoremove_wake_function */
wait_queue_func_t func;
struct list_head task_list; /* 掛入wait_queue_head的掛載點 */
};
Linux中等待隊列的實現思想如下圖所示,當一個任務需要在某個wait_queue_head上睡眠時,將自己的進程控制塊信息封裝到wait_queue中,然後掛載到wait_queue的鏈表中,執行調度睡眠。當某些事件發生後,另一個任務(進程)會喚醒wait_queue_head上的某個或者所有任務,喚醒工作也就是將等待隊列中的任務設置為可調度的狀態,並且從隊列中刪除。
使用等待隊列時首先需要定義一個wait_queue_head,這可以通過DECLARE_WAIT_QUEUE_HEAD宏來完成,這是靜態定義的方法。該宏會定義一個wait_queue_head,並且初始化結構中的鎖以及等待隊列。當然,動態初始化的方法也很簡單,初始化一下鎖及隊列就可以了。
一個任務需要等待某一事件的發生時,通常調用wait_event,該函數會定義一個wait_queue,描述等待任務,並且用當前的進程描述塊初始化wait_queue,然後將wait_queue加入到wait_queue_head中。函數實現流程說明如下:
1、用當前的進程描述塊(PCB)初始化一個wait_queue描述的等待任務。
2、在等待隊列鎖資源的保護下,將等待任務加入等待隊列。
3、判斷等待條件是否滿足,如果滿足,那麼將等待任務從隊列中移出,退出函數。
4、 如果條件不滿足,那麼任務調度,將CPU資源交與其它任務。
5、 當睡眠任務被喚醒之後,需要重復(2)、(3)步驟,如果確認條件滿足,退出等待事件函數。
等待隊列編程接口
1
wait_event
這是一個宏,讓當前任務處於等待事件狀態。輸入參數如下:
@wq:等待隊列
@conditions:等待條件
2
wait_event_timeout
功能與wait_event類似,多了一個超時機制。參數中多了一項超時時間。
3
wait_event_interruptible
這是一個宏,與前兩個宏相比,該宏定義的等待能夠被消息喚醒。如果被消息喚醒,那麼返回- ERESTARTSYS.輸入參數如下:
@wq:等待隊列
@condition:等待條件
@rt:返回值
4
wait_event_interruptible_timeout
與(3)相比,多了超時機制
5
wake_up
喚醒等待隊列中的一個任務
6
wake_up_all
喚醒等待隊列中的所有任務