一文讀懂 | Linux延時(shí)隊(duì)列工作原理
當(dāng)進(jìn)程要獲取某些資源(例如從網(wǎng)卡讀取數(shù)據(jù))的時(shí)候,但資源并沒(méi)有準(zhǔn)備好(例如網(wǎng)卡還沒(méi)接收到數(shù)據(jù)),這時(shí)候內(nèi)核必須切換到其他進(jìn)程運(yùn)行,直到資源準(zhǔn)備好再喚醒進(jìn)程。
waitqueue (等待隊(duì)列)?就是內(nèi)核用于管理等待資源的進(jìn)程,當(dāng)某個(gè)進(jìn)程獲取的資源沒(méi)有準(zhǔn)備好的時(shí)候,可以通過(guò)調(diào)用?add_wait_queue()?函數(shù)把進(jìn)程添加到?waitqueue?中,然后切換到其他進(jìn)程繼續(xù)執(zhí)行。當(dāng)資源準(zhǔn)備好,由資源提供方通過(guò)調(diào)用?wake_up()?函數(shù)來(lái)喚醒等待的進(jìn)程。
等待隊(duì)列初始化
要使用?waitqueue?首先需要聲明一個(gè)?wait_queue_head_t?結(jié)構(gòu)的變量,wait_queue_head_t?結(jié)構(gòu)定義如下:
struct?__wait_queue_head?{
????spinlock_t?lock;
????struct?list_head?task_list;
};
waitqueue?本質(zhì)上是一個(gè)鏈表,而?wait_queue_head_t?結(jié)構(gòu)是?waitqueue?的頭部,lock?字段用于保護(hù)等待隊(duì)列在多核環(huán)境下數(shù)據(jù)被破壞,而?task_list?字段用于保存等待資源的進(jìn)程列表。
可以通過(guò)調(diào)用?init_waitqueue_head()?函數(shù)來(lái)初始化?wait_queue_head_t?結(jié)構(gòu),其實(shí)現(xiàn)如下:
void?init_waitqueue_head(wait_queue_head_t?*q)
{
????spin_lock_init(&q->lock);
????INIT_LIST_HEAD(&q->task_list);
}
初始化過(guò)程很簡(jiǎn)單,首先調(diào)用?spin_lock_init()?來(lái)初始化自旋鎖?lock,然后調(diào)用?INIT_LIST_HEAD()?來(lái)初始化進(jìn)程鏈表。
向等待隊(duì)列添加等待進(jìn)程
要向?waitqueue?添加等待進(jìn)程,首先要聲明一個(gè)?wait_queue_t?結(jié)構(gòu)的變量,wait_queue_t?結(jié)構(gòu)定義如下:
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;
};
下面說(shuō)明一下各個(gè)成員的作用:
flags: 可以設(shè)置為?WQ_FLAG_EXCLUSIVE,表示等待的進(jìn)程應(yīng)該獨(dú)占資源(解決驚群現(xiàn)象)。private: 一般用于保存等待進(jìn)程的進(jìn)程描述符?task_struct。func: 喚醒函數(shù),一般設(shè)置為?default_wake_function()?函數(shù),當(dāng)然也可以設(shè)置為自定義的喚醒函數(shù)。task_list: 用于連接其他等待資源的進(jìn)程。
可以通過(guò)調(diào)用?init_waitqueue_entry()?函數(shù)來(lái)初始化?wait_queue_t?結(jié)構(gòu)變量,其實(shí)現(xiàn)如下:
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;
}
也可以通過(guò)調(diào)用?init_waitqueue_func_entry()?函數(shù)來(lái)初始化為自定義的喚醒函數(shù):
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?結(jié)構(gòu)變量后,可以通過(guò)調(diào)用?add_wait_queue()?函數(shù)把等待進(jìn)程添加到等待隊(duì)列,其實(shí)現(xiàn)如下:
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()?函數(shù)的實(shí)現(xiàn)很簡(jiǎn)單,首先通過(guò)調(diào)用?spin_lock_irqsave()?上鎖,然后調(diào)用?list_add()?函數(shù)把節(jié)點(diǎn)添加到等待隊(duì)列即可。
wait_queue_head_t?結(jié)構(gòu)與?wait_queue_t?結(jié)構(gòu)之間的關(guān)系如下圖:

休眠等待進(jìn)程
當(dāng)把進(jìn)程添加到等待隊(duì)列后,就可以休眠當(dāng)前進(jìn)程,讓出CPU給其他進(jìn)程運(yùn)行,要休眠進(jìn)程可以通過(guò)一下方式:
set_current_state(TASK_INTERRUPTIBLE);
schedule();
代碼?set_current_state(TASK_INTERRUPTIBLE)?可以把當(dāng)前進(jìn)程運(yùn)行狀態(tài)設(shè)置為?可中斷休眠?狀態(tài),調(diào)用?schedule()?函數(shù)可以使當(dāng)前進(jìn)程讓出CPU,切換到其他進(jìn)程執(zhí)行。
喚醒等待隊(duì)列
當(dāng)資源準(zhǔn)備好后,就可以喚醒等待隊(duì)列中的進(jìn)程,可以通過(guò)?wake_up()?函數(shù)來(lái)喚醒等待隊(duì)列中的進(jìn)程。wake_up()?最終會(huì)調(diào)用?__wake_up_common(),其實(shí)現(xiàn)如下:
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;
????}
}
可以看出,喚醒等待隊(duì)列就是變量等待隊(duì)列的等待進(jìn)程,然后調(diào)用喚醒函數(shù)來(lái)喚醒它們。
