linux等待隊列

Linux內核的等待隊列(轉載)
Linux內核的等待隊列是以雙循環鏈表爲基礎數據結構,與進程調度機制緊密結合,能夠用於實現核心的異步事件通知機制。在Linux2.4.21中,等待隊列在源代碼樹include/linux/wait.h中,這是一個通過list_head連接的典型雙循環鏈表,如下圖所示。

在這個鏈表中,有兩種數據結構:等待隊列頭(wait_queue_head_t)和等待隊列項(wait_queue_t)。等待隊列頭和等待隊列項中都包含一個list_head類型的域作爲"連接件"。由於我們只需要對隊列進行添加和刪除操作,並不會修改其中的對象(等待隊列項),因此,我們只需要提供一把保護整個基礎設施和所有對象的鎖,這把鎖保存在等待隊列頭中,爲wq_lock_t類型。在實現中,可以支持讀寫鎖(rwlock)或自旋鎖(spinlock)兩種類型,通過一個宏定義來切換。如果使用讀寫鎖,將wq_lock_t定義爲rwlock_t類型;如果是自旋鎖,將wq_lock_t定義爲spinlock_t類型。無論哪種情況,分別相應設置wq_read_lock、wq_read_unlock、 wq_read_lock_irqsave、wq_read_unlock_irqrestore、wq_write_lock_irq、 wq_write_unlock、wq_write_lock_irqsave和wq_write_unlock_irqrestore等宏。
一、定義:
/include/linux/wait.h
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
二、作用:
在內核裏面,等待隊列是有很多用處的,尤其是在中斷處理、進程同步、定時等場合。可以使用等待隊列在實現阻塞進程的喚醒。它以隊列爲基礎數據結構,與進程調度機制緊密結合,能夠用於實現內核中的異步事件通知機制,同步對系統資源的訪問等。
三、字段詳解:
1、spinlock_t lock;
在對task_list與操作的過程中,使用該鎖實現對等待隊列的互斥訪問。
2、srtuct list_head_t task_list;
雙向循環鏈表,存放等待的進程。
三、操作:
1、定義並初始化:
(1)
wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue);
直接定義並初始化。init_waitqueue_head()函數會將自旋鎖初始化爲未鎖,等待隊列初始化爲空的雙向循環鏈表。
(2)
DECLARE_WAIT_QUEUE_HEAD(my_queue);
定義並初始化,相當於(1)。
(3)定義等待隊列:
DECLARE_WAITQUEUE(name,tsk);
注意此處是定義一個wait_queue_t類型的變量name,並將其private與設置爲tsk。wait_queue_t類型定義如下:
   struct __wait_queue {
   unsigned int flags;
   #define WQ_FLAG_EXCLUSIVE 0x01
   void *private;
   wait_queue_func_t func;
   struct list_head task_list;
   };
其中flags域指明該等待的進程是互斥進程還是非互斥進程。其中0是非互斥進程,WQ_FLAG_EXCLUSIVE(0x01)是互斥進程。等待隊列(wait_queue_t)和等待對列頭(wait_queue_head_t)的區別是等待隊列是等待隊列頭的成員。也就是說等待隊列頭的task_list域鏈接的成員就是等待隊列類型的(wait_queue_t)。
2、(從等待隊列頭中)添加/移出等待隊列:
(1)add_wait_queue()函數:
void fastcall 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);
}
設置等待的進程爲非互斥進程,並將其添加進等待隊列頭(q)的隊頭中。
void fastcall add_wait_queue_exclusive(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_tail(q, wait);
spin_unlock_irqrestore(&q->lock, flags);
}
該函數也和add_wait_queue()函數功能基本一樣,只不過它是將等待的進程(wait)設置爲互斥進程。
(2)remove_wait_queue()函數:
void fastcall remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
spin_lock_irqsave(&q->lock, flags);
__remove_wait_queue(q, wait);
spin_unlock_irqrestore(&q->lock, flags);
}
在等待的資源或事件滿足時,進程被喚醒,使用該函數被從等待頭中刪除。
3、等待事件:
(1)wait_event()宏:
#define wait_event(wq, condition) /
do { /
if (condition) /
break; /
__wait_event(wq, condition); /
} while (0)
#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)
在等待會列中睡眠直到condition爲真。在等待的期間,進程會被置爲TASK_UNINTERRUPTIBLE進入睡眠,直到condition變量變爲真。每次進程被喚醒的時候都會檢查condition的值.
(2)wait_event_interruptible()函數:
和wait_event()的區別是調用該宏在等待的過程中當前進程會被設置爲TASK_INTERRUPTIBLE狀態.在每次被喚醒的時候,首先檢查condition是否爲真,如果爲真則返回,否則檢查如果進程是被信號喚醒,會返回-ERESTARTSYS錯誤碼.如果是condition爲真,則返回0.
(3)wait_event_timeout()宏:
也與wait_event()類似.不過如果所給的睡眠時間爲負數則立即返回.如果在睡眠期間被喚醒,且condition爲真則返回剩餘的睡眠時間,否則繼續睡眠直到到達或超過給定的睡眠時間,然後返回0.
(4)wait_event_interruptible_timeout()宏:
與wait_event_timeout()類似,不過如果在睡眠期間被信號打斷則返回ERESTARTSYS錯誤碼.
(5) wait_event_interruptible_exclusive()宏
同樣和wait_event_interruptible()一樣,不過該睡眠的進程是一個互斥進程.
5、喚醒隊列:
(1)wake_up()函數:
#define wake_up(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1, NULL)
void fastcall __wake_up(wait_queue_head_t *q, unsigned int mode,
int nr_exclusive, void *key)
{
unsigned long flags;
spin_lock_irqsave(&q->lock, flags);
__wake_up_common(q, mode, nr_exclusive, 0, key);
spin_unlock_irqrestore(&q->lock, flags);
}
static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
int nr_exclusive, int sync, void *key)
{
struct list_head *tmp, *next;
list_for_each_safe(tmp, next, &q->task_list) {
wait_queue_t *curr = list_entry(tmp, wait_queue_t, task_list);
unsigned flags = curr->flags;
if (curr->func(curr, mode, sync, key) &&
(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
break;
}
}
喚醒等待隊列.可喚醒處於TASK_INTERRUPTIBLE和TASK_UNINTERUPTIBLE狀態的進程,和wait_event/wait_event_timeout成對使用.
(2)wake_up_interruptible()函數:
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
和wake_up()唯一的區別是它只能喚醒TASK_INTERRUPTIBLE狀態的進程.,與wait_event_interruptible/wait_event_interruptible_timeout/ wait_event_interruptible_exclusive成對使用.
(3)
#define wake_up_all(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 0, 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)
這些也基本都和wake_up/wake_up_interruptible一樣.
6、在等待隊列上睡眠:
(1)sleep_on()函數:
void __sched sleep_on(wait_queue_head_t *q)                                                                                  
{
       unsigned long flags;
       wait_queue_t wait;
       init_waitqueue_entry(&wait, current);
       current->state = TASK_UNINTERRUPTIBLE;
       sleep_on_head(q, &wait, &flags);
       schedule();
       sleep_on_tail(q, &wait, &flags);
}
該函數的作用是定義一個等待隊列(wait),並將當前進程添加到等待隊列中(wait),然後將當前進程的狀態置爲TASK_UNINTERRUPTIBLE,並將等待隊列(wait)添加到等待隊列頭(q)中。之後就被掛起直到資源可以獲取,才被從等待隊列頭(q)中喚醒,從等待隊列頭中移出。在被掛起等待資源期間,該進程不能被信號喚醒。
(2)sleep_on_timeout()函數:
long __sched sleep_on_timeout(wait_queue_head_t *q, long timeout)
{
       unsigned long flags;
       wait_queue_t wait
       init_waitqueue_entry(&wait, current);
       current->state = TASK_UNINTERRUPTIBLE;
       sleep_on_head(q, &wait, &flags);
       timeout = schedule_timeout(timeout);
       sleep_on_tail(q, &wait, &flags);
       return timeout;
}
與sleep_on()函數的區別在於調用該函數時,如果在指定的時間內(timeout)沒有獲得等待的資源就會返回。實際上是調用schedule_timeout()函數實現的。值得注意的是如果所給的睡眠時間(timeout)小於0,則不會睡眠。該函數返回的是真正的睡眠時間。
(3)interruptible_sleep_on()函數:
void __sched interruptible_sleep_on(wait_queue_head_t *q)
{
unsigned long flags;
wait_queue_t wait;
init_waitqueue_entry(&wait, current);
current->state = TASK_INTERRUPTIBLE;
sleep_on_head(q, &wait, &flags);
schedule();
sleep_on_tail(q, &wait, &flags);
}
該函數和sleep_on()函數唯一的區別是將當前進程的狀態置爲TASK_INTERRUPTINLE,這意味在睡眠如果該進程收到信號則會被喚醒。
(4)interruptible_sleep_on_timeout()函數:
long __sched
interruptible_sleep_on_timeout(wait_queue_head_t *q, long timeout)
{
unsigned long flags;
wait_queue_t wait;
init_waitqueue_entry(&wait, current);
current->state = TASK_INTERRUPTIBLE;
sleep_on_head(q, &wait, &flags);
timeout = schedule_timeout(timeout);
sleep_on_tail(q, &wait, &flags);
return timeout;
}
類似於sleep_on_timeout()函數。進程在睡眠中可能在等待的時間沒有到達就被信號打斷而被喚醒,也可能是等待的時間到達而被喚醒。
以上四個函數都是讓進程在等待隊列上睡眠,不過是小有詫異而已。在實際用的過程中,根據需要選擇合適的函數使用就是了。例如在對軟驅數據的讀寫中,如果設備沒有就緒則調用sleep_on()函數睡眠直到數據可讀(可寫),在打開串口的時候,如果串口端口處於關閉狀態則調用interruptible_sleep_on()函數嘗試等待其打開。在聲卡驅動中,讀取聲音數據時,如果沒有數據可讀,就會等待足夠常的時間直到可讀取。


本文來自ChinaUnix博客,如果查看原文請點:http://blog.chinaunix.net/u2/82249/showart_1985328.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章