讀一下wait_event_interruptible()的源碼,不難發現這個函數先將
當前進程的狀態設置成TASK_INTERRUPTIBLE,然後調用schedule(),
而schedule()會將位於TASK_INTERRUPTIBLE狀態的當前進程從runqueue
隊列中刪除。從runqueue隊列中刪除的結果是,當前這個進程將不再參
與調度,除非通過其他函數將這個進程重新放入這個runqueue隊列中,
這就是wake_up()的作用了。
由於這一段代碼位於一個由condition控制的for(;;)循環中,所以當由
shedule()返回時(當然是被wake_up之後,通過其他進程的schedule()而
再次調度本進程),如果條件condition不滿足,本進程將自動再次被設
置爲TASK_INTERRUPTIBLE狀態,接下來執行schedule()的結果是再次被
從runqueue隊列中刪除。這時候就需要再次通過wake_up重新添加到
runqueue隊列中。
如此反覆,直到condition爲真的時候被wake_up.
可見,成功地喚醒一個被wait_event_interruptible()的進程,需要滿足:
在 1)condition爲真的前提下,2) 調用wake_up()。
所以,如果你僅僅修改condition,那麼只是滿足其中一個條件,這個時候,
被wait_event_interruptible()起來的進程尚未位於runqueue隊列中,因
此不會被 schedule。這個時候只要wake_up一下就立刻會重新進入運行調度。
2. 關於wait_event_interruptible的返回值
根據 wait_event_interruptible 的宏定義知:
1) 條件condition爲真時調用這個函數將直接返回0,而當前進程不會
被 wait_event_interruptible和從runqueue隊列中刪除。
2) 如果要被wait_event_interruptible的當前進程有nonblocked pending
signals, 那麼會直接返回-ERESTARTSYS(i.e. -512),當前進程不會
被wait_event_interruptible 和從runqueue隊列中刪除。
3) 其他情況下,當前進程會被正常的wait_event_interruptible,並從
runqueue隊列中刪除,進入TASK_INTERRUPTIBLE狀態退出運行調度,
直到再次被喚醒加入runqueue隊列中後而參與調度,將正常返回0。
附1:wait_event_interruptible 宏
#define wait_event_interruptible(wq, condition) \
({ \
int __ret = 0; \
if (!(condition)) \
__wait_event_interruptible(wq, condition, __ret); \
__ret; \
})
注: C語言中{a,b, ..., x}的的值等於最後一項,即x,因此上述
宏的值是 __ret。
附2:wait_event_interruptible()和 wake_up的等效代碼
wait_event_interruptible(wq, condition) /*等效沒有考慮返回值*/
{
if (!(condition))
{
wait_queue_t _ _wait;
init_waitqueue_entry(&_ _wait, current);
add_wait_queue(&wq, &_ _wait);
for (;;)
{
set_current_state(TASK_INTERRUPTIBLE);
if (condition)
break;
schedule(); /* implicit call: del_from_runqueue(current)*/
}
current->state = TASK_RUNNING;
remove_wait_queue(&wq, &_ _wait);
}
}
void wake_up(wait_queue_head_t *q)
{
struct list_head *tmp;
wait_queue_t *curr;
list_for_each(tmp, &q->task_list)
{
curr = list_entry(tmp, wait_queue_t, task_list);
wake_up_process(curr->task);
/* implicit call: add_to_runqueue(curr->task);*/
if (curr->flags)
break;
}
}
什麼是等待隊列?
在軟件開發中任務經常由於某種條件沒有得到滿足而不得不進入睡眠狀態,然後等待條件得到滿足的時候再繼續運行,進入運行狀態。這種需求需要等待隊列機制的支持。Linux中提供了等待隊列的機制,該機制在內核中應用很廣泛。
在Linux內核中使用等待隊列的過程很簡單,首先定義一個wait_queue_head,然後如果一個task想等待某種事件,那麼調用wait_event(等待隊列,事件)就可以了。
等待隊列應用廣泛,但是內核實現卻十分簡單。其涉及到兩個比較重要的數據結構:
1) __wait_queue_head,該結構描述了等待隊列的鏈頭,其包含一個鏈表和一個原子鎖,結構定義如下:
struct__wait_queue_head {
spinlock_tlock;
struct list_headtask_list;
};
2) __wait_queue,該結構是對一個等待任務的抽象。每個等待任務都會抽象成一個wait_queue,並且掛載到wait_queue_head上。該結構定義如下:
struct__wait_queue {
unsigned intflags;
void*private;
wait_queue_func_tfunc;
struct list_headtask_list;
};
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 |
喚醒等待隊列中的所有任務 |