歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux技術 >> linux等待隊列

linux等待隊列

日期:2017/3/3 12:59:39   编辑: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 __schedinterruptible_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()函數嘗試等待其打開。在聲卡驅動中,讀取聲音數據時,如果沒有數據可讀,就會等待足夠常的時間直到可讀取。上文來自:http://www.360doc.com/content/14/0605/09/10249440_384090567.shtml

Copyright © Linux教程網 All Rights Reserved