http://blog.csdn.net/yyttiao/article/details/7877168
很多時候我們在使用等待隊列的時候,其實並不知道它的內部是怎麼實現的。爲什麼要用add_wait_queue來增加
而後又爲什麼要使用wait_queue_head_t ,sechedule之後怎麼返回到之前的位置,喚醒又做了些什麼事情等等。。這裏就來簡單的剖析下等待隊列的內部實現原理
其實我在使用等待隊列的時候有這樣的一個問題,並且困擾我很久,先看一段代碼
DECLARE_WAITQUEUE(wait,current);
add_wait_queue(&devp->w_wait,&wait);
while (devp->current_size == GLOBAMEM_SIZE) { //無數據可讀,進入睡眠
__set_current_state(TASK_INTERRUPTIBLE); //修改當前進程狀態爲淺睡眠
schedule(); //重新調度進程
if (signal_pending(current)) {
ret = -ERESTARTSYS;
goto out2;
}
}
/* *********一般情況下,也是大多數情況下,應該說必須這麼做吧********** */
在其它位置上肯定有會一個喚醒寫上面的等待隊列,否則就沒法執行了。。。
wake_up_interruptible(&devp->w_wait);
之前一致覺得奇怪,爲什麼add_wait_queue之後就可以被喚醒呢。。爲什麼在其它地方調wake_up_interruptible就可以重新讓等待的
進程被調度起來呢。。
其實可能並不是所有的人都跟我一樣,會迷茫。。但是久而久之我就明白了。
首先看一下wait_queue_t
typedef struct __wait_queue wait_queue_t;
struct __wait_queue {
unsigned int flags;
#define WQ_FLAG_EXCLUSIVE 0x01
/* 取值爲WQ_FLAG_EXCLUSIVE(=1)表示互斥進程,由內核有選擇的喚醒.爲0時表示非互斥進程,由內核在
**事件發生時喚醒所有等待進程.
**/
void *private;
wait_queue_func_t func;
struct list_head task_list;
};
在看一下初始化宏
#define __WAITQUEUE_INITIALIZER(name, tsk) { \
.private = tsk, \
.func = default_wake_function, \
.task_list = { NULL, NULL } }
#define DECLARE_WAITQUEUE(name, tsk) \
wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
DECLARE_WAITQUEUE(wait,current);
根據上面的宏不難看出,這裏將current放到了.private成員上。這裏還有一個指定的默認喚醒函數的指針。
接下來看一個add_wait_queue函數接口。看該函數之前要先看下wait_queue_head_t
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
其實就是一個內核鏈表。將一連串的等待隊列連接起來而已,在修改鏈表的時候用lock鎖住。就這麼簡單下面就看函數
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
wait->flags &= ~WQ_FLAG_EXCLUSIVE;
spin_lock_irqsave(&q->lock, flags);
__add_wait_queue(q, wait);
spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(add_wait_queue);
static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)
{
list_add(&new->task_list, &head->task_list);
}
該函數其實就是簡單的將等待隊列放到了鏈表中請一定要記住上面那些代碼的流程,這裏複習下。。
1:將wait_queue_t通過list_add放到wait_queue_head_t內,wait_queue_t內包含當前的任務結構體current
2:通過宏__set_current_state(state_value)來改變任務狀態
3:然後調用schedule進行任務調度(由於當前任務狀態已經不是RUNING了,所以不會被調度執行)
下面來看一些wait_event相關函數。其實明白上面的流程之後很容易理解wait_event在做些什麼
#define wait_event(wq, condition)
do {
/* 首先判斷條件 */
if (condition)
break;
/* 此處開始進入等待切換 */
__wait_event(wq, condition);
} while (0)
#define __wait_event(wq, condition)
do {
/* 創建等待隊列 */
DEFINE_WAIT(__wait);
for (;;) {
/* 切換任務狀態,並加入wait_queue_t到wait_queue_head_t */
prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);
/* 再一次判斷條件 */
if (condition)
break;
/* 調度任務 */
schedule();
}
/* 結束之後,將任務狀態再一次切換到TASK_RUNING 然後將wait_queue_t從wait_queue_head_t中移除 */
finish_wait(&wq, &__wait);
} while (0)
帶有timeout超時的等待隊列,其基本相似,只是調度接口換了schedule_timeout而已,如下
#define wait_event_timeout(wq, condition, timeout)
({
long __ret = timeout;
if (!(condition))
__wait_event_timeout(wq, condition, __ret);
__ret;
})
#define __wait_event_timeout(wq, condition, ret)
do {
DEFINE_WAIT(__wait);
for (;;) {
prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);
if (condition)
break;
ret = schedule_timeout(ret);
if (!ret)
break;
}
finish_wait(&wq, &__wait);
} while (0)
這裏我不全部說明。具體的請參閱內核源碼
#define wait_event_interruptible(wq, condition)
#define wait_event_interruptible_timeout(wq, condition, timeout)
總結下
函數 | 調度函數 | signal_pending |
wait_event | schedule | N |
wait_event_interruptible | schedule | Y |
wait_event_timeout | schedule_timeout | N |
wait_event_interruptible_timeout | schedule_timeout | Y |
如何讓任務等待相信已經明白了吧。。下面該是喚醒的部分了,請看 淺談等待隊列的內部實現(二)
http://blog.csdn.net/yyttiao/article/details/7877168