1. 程式人生 > >Linux核心等待佇列詳解

Linux核心等待佇列詳解

等待佇列用於管理應等待某個條件成立或者事件發生而防人之心不可無的程序。程序發出的請求暫時無法滿足的時候,需要將程序阻塞直到條件成立。核心將因相同原因而阻塞的程序集中在一個佇列上,該佇列就是等待佇列,對於每個需要等待的事件都有一個相應的等待佇列。等待佇列採用連結串列的方式來儲存,各個阻塞程序作為節點儲存在連結串列中。連結串列頭的型別是wait_queue_head_t,節點型別為wait_queue_t,如下所示:

//linux-3.13/include/linux/wait.h
struct __wait_queue_head {
    spinlock_t      lock;            //自旋鎖
struct list_head task_list; //指向佇列的連結串列(成員是wait_queue_t型別) }; typedef struct __wait_queue_head wait_queue_head_t; typedef struct __wait_queue wait_queue_t; struct __wait_queue { unsigned int flags; //喚醒程序的方式,0表示非互斥方式,1表示互斥方式(一個等待佇列中flags標誌為1的程序只能一次喚醒一個,即互斥) #define WQ_FLAG_EXCLUSIVE 0x01
void *private; //指向阻塞程序的task_struct wait_queue_func_t func; //喚醒函式(根據喚醒方式的不同而執行不同的喚醒操作) struct list_head task_list; //構成等待佇列的雙向連結串列 };

其中預設的喚醒函式wait_queue_func_t是對try_to_wake_up()的簡單封裝。

等待佇列的使用包括兩個步驟,等待和喚醒。當程序需要睡眠時,呼叫wait_event()將自己加入等待佇列,讓出CPU,比如在向塊裝置發出請求之後由於資料不能立即返回,所以需要睡眠,此時就呼叫wait_event()

。當事件到達後,則利用wake_up()等函式喚醒等待佇列中的程序。wait_event()巨集的程式碼如下:

#define wait_event(wq, condition)                   \
do {                                    \
    if (condition)                          \
        break;                          \
    __wait_event(wq, condition);                    \
} while (0)

#define __wait_event(wq, condition)                 \
    (void)___wait_event(wq, condition, TASK_UNINTERRUPTIBLE, 0, 0,  \
                schedule())

#define ___wait_event(wq, condition, state, exclusive, ret, cmd)    \
({                                  \
    __label__ __out;                        \
    wait_queue_t __wait;                        \
    long __ret = ret;                       \
                                    \
    INIT_LIST_HEAD(&__wait.task_list);              \
    if (exclusive)                          \
        __wait.flags = WQ_FLAG_EXCLUSIVE;           \
    else                                \
        __wait.flags = 0;                   \
                                    \
    for (;;) {                          \                      //迴圈等待
        long __int = prepare_to_wait_event(&wq, &__wait, state);\     //初始化__wait變數,並將其加入wq中,然後設定程序的執行狀態為TASK_UNINTERRUPTIBLE
                                    \
        if (condition)                      \                  //如果條件滿足,則跳出迴圈,否則繼續迴圈下去
            break;                      \
                                    \
        if (___wait_is_interruptible(state) && __int) {     \
            __ret = __int;                  \
            if (exclusive) {                \
                abort_exclusive_wait(&wq, &__wait,  \
                             state, NULL);  \
                goto __out;             \
            }                       \
            break;                      \
        }                           \
                                    \
        cmd;                            \                       //傳入的cmd是schedule()
    }                               \
    finish_wait(&wq, &__wait);                  \               //設定程序的執行狀態為TASK_RUNNING,並將程序從等待佇列wq中刪除
__out:  __ret;                              \
})

從程式碼中可以看出,只要所等待的條件(condition)不滿足應付一直迴圈等待,直到條件滿足後執行finish_wait()。其中condition是傳入的引數,例如軟盤驅動程式碼中會有wait_event(command_done, command_status >= 2);。在等待佇列上的程序可能因為wake_up()而喚醒,wake_up()將會執行__wake_queue->func()喚醒函式,預設是default_wake_function()default_wake_function()呼叫try_to_wake_up()將程序更改為可執行狀態並設定待排程標記。wake_up()的程式碼如下:

//linux-3.13/include/linux/wait.h
#define wake_up(x)          __wake_up(x, TASK_NORMAL, 1, NULL)

void __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 wake_flags, void *key)
{
    wait_queue_t *curr, *next;

    //遍歷等待佇列q
    list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
        unsigned flags = curr->flags;

        //執行wait_queue_t變數中註冊的喚醒函式
        if (curr->func(curr, mode, wake_flags, key) &&
                (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
            break;
    }
}

等待佇列的的使用在驅動程式碼中會有很多,另外,Java中Object.wait()/notify()方法的實現也與等待佇列有關。