1. 程式人生 > 實用技巧 >Linux等待佇列原理與實現

Linux等待佇列原理與實現

當程序要獲取某些資源(例如從網絡卡讀取資料)的時候,但資源並沒有準備好(例如網絡卡還沒接收到資料),這時候核心必須切換到其他程序執行,直到資源準備好再喚醒程序。

waitqueue (等待佇列)就是核心用於管理等待資源的程序,當某個程序獲取的資源沒有準備好的時候,可以通過呼叫add_wait_queue()函式把程序新增到waitqueue中,然後切換到其他程序繼續執行。當資源準備好,由資源提供方通過呼叫wake_up()函式來喚醒等待的程序。

等待佇列初始化

要使用waitqueue首先需要宣告一個wait_queue_head_t結構的變數,wait_queue_head_t結構定義如下:

struct __wait_queue_head {
    spinlock_t lock;
    struct list_head task_list;
};

waitqueue本質上是一個連結串列,而wait_queue_head_t結構是waitqueue的頭部,lock欄位用於保護等待佇列在多核環境下資料被破壞,而task_list欄位用於儲存等待資源的程序列表。

可以通過呼叫init_waitqueue_head()函式來初始化wait_queue_head_t結構,其實現如下:

void init_waitqueue_head(wait_queue_head_t *q)
{
    spin_lock_init(&q->lock);
    INIT_LIST_HEAD(&q->task_list);
}

初始化過程很簡單,首先呼叫spin_lock_init()來初始化自旋鎖lock,然後呼叫INIT_LIST_HEAD()來初始化程序連結串列。

向等待佇列新增等待程序

要向waitqueue新增等待程序,首先要宣告一個wait_queue_t結構的變數,wait_queue_t結構定義如下:

typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int sync, void *key);

struct __wait_queue {
    unsigned int flags;
    void *private;
    wait_queue_func_t func;
    struct list_head task_list;
};

下面說明一下各個成員的作用:

  1. flags: 可以設定為WQ_FLAG_EXCLUSIVE,表示等待的程序應該獨佔資源(解決驚群現象)。

  2. private: 一般用於儲存等待程序的程序描述符task_struct。

  3. func: 喚醒函式,一般設定為default_wake_function()函式,當然也可以設定為自定義的喚醒函式。

  4. task_list: 用於連線其他等待資源的程序。

可以通過呼叫init_waitqueue_entry()函式來初始化wait_queue_t結構變數,其實現如下:

static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)
{
    q->flags = 0;
    q->private = p;
    q->func = default_wake_function;
}

也可以通過呼叫init_waitqueue_func_entry()函式來初始化為自定義的喚醒函式:

static inline void init_waitqueue_func_entry(wait_queue_t *q, wait_queue_func_t func)
{
    q->flags = 0;
    q->private = NULL;
    q->func = func;
}

初始化完wait_queue_t結構變數後,可以通過呼叫add_wait_queue()函式把等待程序新增到等待佇列,其實現如下:

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

static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)
{
    list_add(&new->task_list, &head->task_list);
}

add_wait_queue()函式的實現很簡單,首先通過呼叫spin_lock_irqsave()上鎖,然後呼叫list_add()函式把節點新增到等待佇列即可。

wait_queue_head_t結構與wait_queue_t結構之間的關係如下圖:

電腦刺繡繡花廠 http://www.szhdn.com 廣州品牌設計公司https://www.houdianzi.com

休眠等待程序

當把程序新增到等待佇列後,就可以休眠當前程序,讓出CPU給其他程序執行,要休眠程序可以通過以下方式:

set_current_state(TASK_INTERRUPTIBLE);
schedule();

程式碼set_current_state(TASK_INTERRUPTIBLE)可以把當前程序執行狀態設定為可中斷休眠狀態,呼叫schedule()函式可以使當前程序讓出CPU,切換到其他程序執行。

喚醒等待佇列

當資源準備好後,就可以喚醒等待佇列中的程序,可以通過wake_up()函式來喚醒等待佇列中的程序。wake_up()最終會呼叫__wake_up_common(),其實現如下:

static void __wake_up_common(wait_queue_head_t *q,
    unsigned int mode, int nr_exclusive, int sync, void *key)
{
    wait_queue_t *curr, *next;

    list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
        unsigned flags = curr->flags;

        if (curr->func(curr, mode, sync, key) &&
                (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
            break;
    }
}

可以看出,喚醒等待佇列就是變數等待佇列的等待程序,然後呼叫喚醒函式來喚醒它們。