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;
};
下面說明一下各個成員的作用:
-
flags: 可以設定為WQ_FLAG_EXCLUSIVE,表示等待的程序應該獨佔資源(解決驚群現象)。
-
private: 一般用於儲存等待程序的程序描述符task_struct。
-
func: 喚醒函式,一般設定為default_wake_function()函式,當然也可以設定為自定義的喚醒函式。
-
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;
}
}
可以看出,喚醒等待佇列就是變數等待佇列的等待程序,然後呼叫喚醒函式來喚醒它們。