07.20 linux等待隊列 wait

我們先講如何利用wait_queue,然後再講wait_queue的內核原理

1. 如何利用wait_queue

等待隊列用於使進程等待某一特定的事件發生而無需頻繁的輪詢

在不需要執行任務的時候,我們就讓任務進程休眠,直到條件改變時,我們再喚醒他,執行完畢後繼續讓它睡眠

先來看一個簡單的例子:

1)首先初始化等待隊列頭

wait_queue_head_t my_queue;

init_waitqueue_head(&my_queue);

2)調用wait_event_interruptible如果condition為1則說明需要的條件都滿足進程不睡眠,如果condition不為1則說明需要的條件不滿足,進程立即進入睡眠等待被喚醒,被喚醒後則開始執行任務,任務執行完畢繼續進入睡眠

if( wait_event_interruptible(queue, condition) )

{

//執行任務

}

3)當需要等待中的進程執行任務的時候,則我們喚醒wait_queue

wake_up_interruptible(wait_queue_head_t *queue)

linux等待隊列 wait_queue的使用

2.下面我們具體講一下wait_queue的基本原理和內核實現

一、什麼是睡眠

對於一個進程"睡眠"意味著什麼? 當一個進程被置為睡眠, 它被標識為處於一個特殊的狀態並且從調度器的運行隊列中去除. 直到發生某些事情改變了那個狀態, 這個進程將不被在任何 CPU 上調度, 並且, 因此, 將不會運行. 一個睡著的進程已被擱置到系統的一邊, 等待以後發生事件.

LDD3說得很玄乎,睡眠是“自願調度”,其實就是將當前進程的狀態設置為 TASK_INTERRUPTIBLE 等狀態,然後schedule() 讓出CPU1,讓調度器重新選擇一個進程來執行。

對於一個 Linux 驅動使一個進程睡眠是一個容易做的事情. 但是, 有幾個規則必須記住以安全的方式編碼睡眠.

這些規則的第一個是: 當你運行在原子上下文時不能睡眠.

一個另外的相關的點, 當然, 是你的進程不能睡眠除非確信其他人, 在某處的, 將喚醒它.

做喚醒工作的代碼必須也能夠找到你的進程來做它的工作. 確保一個喚醒發生, 是深入考慮你的代碼和對於每次睡眠, 確切知道什麼系列的事件將結束那次睡眠.使你的進程可能被找到, 真正地, 通過一個稱為等待隊列的數據結構實現的. 一個等待隊列就是它聽起來的樣子:一個進程列表, 都等待一個特定的事件.

二、如何睡眠

在 Linux 中, 一個等待隊列由一個"等待隊列頭"來管理, 一個 wait_queue_head_t 類型的結構, 定義在<linux>中. 一個等待隊列頭可被定義和初始化, 使用:/<linux>

DECLARE_WAIT_QUEUE_HEAD(name);

或者動態地, 如下:

wait_queue_head_t my_queue;

init_waitqueue_head(&my_queue);

1、簡單睡眠

Linux 內核中睡眠的最簡單方式是一個宏定義, 稱為 wait_event(有幾個變體); 它結合了處理睡眠的細節和進程在等待的條件的檢查. wait_event 的形式是:

wait_event(queue, condition)

wait_event_interruptible(queue, condition)

wait_event_timeout(queue, condition, timeout)

wait_event_interruptible_timeout(queue, condition, timeout)

這些東西如何使用?queue 是等待隊列頭,condition 是條件,如果調用 wait_event 前 condition == 0 ,則調用 wait_event 之後,當前進程就會休眠。

那麼它們四個又有什麼不同?

wait_event:將當前進程的狀態設置為 TASK_UNINTERRUPTIBLE ,然後 schedule()

wait_event_interruptible: TASK_INTERRUPTIBLE ,然後 schedule()

wait_event_timeout: TASK_UNINTERRUPTIBLE ,然後 schedule_timeout()

wait_event_interruptible_timeout: TASK_INTERRUPTIBLE , 然後 schedule_timeout()

TASK_INTERRUPTIBLE 與 TASK_UNINTERRUPTIBLE 區別在於,它的休眠是否會被信號打斷,別的進程發來一個信號比如 kill ,TASK_INTERRUPTIBLE 就會醒來去處理。然而 TASK_UNINTERRUPTIBLE 不會。schedule(),進程調度,而schedule_timeout()進行調度之後,一定時間後自動喚醒

對應於不同的進程狀態,使用不同的喚醒函數:

void wake_up(wait_queue_head_t *queue);

void wake_up_interruptible(wait_queue_head_t *queue);

喚醒時很有意思,比如你調用 wake_up 去喚醒一個使用 wait_event 等,進入休眠的進程,喚醒之後,它會判斷 condition 是否為真,如果還是假的繼續睡眠

。至於為什麼這個樣子,後面分析代碼就會明白。

2、手動睡眠

DECLARE_WAITQUEUE(name, tsk) 創建一個等待隊列:

tsk一般為當前進行current. 這個宏定義並初始化一個名為name的等待隊列.

將等待隊列頭 加入/移除 等待隊列:

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

void add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait);

void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

設置進程狀態:

set_current_state(TASK_INTERRUPTIBLE) 等

進程調度:

schedule() 或者 schedule_timeout()

三、內核如何實現

以 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 (;;) {

prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);

if (condition)

break;

schedule();

}

finish_wait(&wq, &__wait);

} while (0)

#define DEFINE_WAIT(name)

wait_queue_t name = {

.private = current,

.func = autoremove_wake_function,

.task_list = LIST_HEAD_INIT((name).task_list),

}

typedef struct __wait_queue 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;

};

舉個例子:宏展開之後

__wait_event(wq, condition);

wait_queue_t __wait = {

.private = current,

.func = autoremove_wake_function,

.task_list = LIST_HEAD_INIT((__wait).task_list),

}

for (;;) {

prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);

if (condition)

break;

schedule();

}

finish_wait(&wq, &__wait);

其實,它定義了一個叫 __wait 的等待隊列,private 指向當前進程的 task_struct 結構體(喚醒的時候好知道是哪個進程),然後調用 prepare_to_wait 將等待隊列頭加入到等待隊列中去,並設置當前進程的狀態為TASK_UNINTERRUPTIBLE。然後,如果 condition 為假,則schedule(),進程調度的時候,當前進程的狀態不是 TASK_RUNNING 必然要被移除 “運行隊列”,也就永遠不會被調度除非直到醒來。如果 condition 為真,那麼finish_wait 會把之前的工作都還原,你繼續執行吧,你要的條件都滿足了,你還休眠個屁!

涉及的函數代碼貼一貼

void fastcall

prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)

{

unsigned long flags;

wait->flags &= ~WQ_FLAG_EXCLUSIVE;

spin_lock_irqsave(&q->lock, flags);

if (list_empty(&wait->task_list))

__add_wait_queue(q, wait);

/*

* don't alter the task state if this is just going to

* queue an async wait queue callback

*/

if (is_sync_wait(wait))

set_current_state(state);

spin_unlock_irqrestore(&q->lock, flags);

}

void fastcall finish_wait(wait_queue_head_t *q, wait_queue_t *wait)

{

unsigned long flags;

__set_current_state(TASK_RUNNING);

if (!list_empty_careful(&wait->task_list)) {

spin_lock_irqsave(&q->lock, flags);

list_del_init(&wait->task_list);

spin_unlock_irqrestore(&q->lock, flags);

}

}

下面來看看如何喚醒的

#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;

}

}

此時會調用到,我們在等待隊列裡指定的那個 func 函數,也就是 autoremove_wake_function

int autoremove_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key)

{

int ret = default_wake_function(wait, mode, sync, key);

if (ret)

list_del_init(&wait->task_list);

return ret;

}

int default_wake_function(wait_queue_t *curr, unsigned mode, int sync,

void *key)

{

return try_to_wake_up(curr->private, mode, sync);

}

最終調用到 default_wake_function 來喚醒 等待隊列裡 private 裡指定的那個進程。然後,移除將等待隊列頭移除等待隊列。try_to_wake_up ,會將 要喚醒進程的 進程狀態設置為 TASK_RUNNING ,然後放到 “運行隊列”中。

有意思的是:

我們休眠時,schedule() 在哪裡? TMD 居然在 for 循環裡

for (;;) { \\

prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); \\

if (condition) \\

break; \\

schedule(); \\

}

喚醒之後,那麼又開始了 prepare_to_wait ,判斷 condition ....顯然 condition 為真,才會真正的 喚醒。

理解了他們,對於手動休眠也就很明白了。手動休眠就不用判斷什麼 condition 了。

DECLARE_WAITQUEUE(name, tsk)

tsk一般為當前進行current. 這個宏定義並初始化一個名為name的等待隊列.

#define DECLARE_WAITQUEUE(name, tsk)

wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)

#define __WAITQUEUE_INITIALIZER(name, tsk) {

.private = tsk,

.func = default_wake_function,

.task_list = { NULL, NULL } }

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

void add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait);

void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

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);

}

set_current_state(TASK_INTERRUPTIBLE);

schedule();

簡單明瞭:

1、創建等待隊列、等待隊列頭

2、將等待隊列頭加入到等待隊列中去

3、設置當前進程的進程狀態

4、進程調度~

原文地址:https://blog.csdn.net/lizuobin2/article/details/51785812


分享到:


相關文章: