我們先講如何利用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)
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
閱讀更多 芹澤多魔雄 的文章