一文讀懂Linux延時(shí)隊(duì)列工作原理
本文轉(zhuǎn)載自微信公眾號(hào)「Linux內(nèi)核那些事」,作者songsong001 。轉(zhuǎn)載本文請(qǐng)聯(lián)系Linux內(nèi)核那些事公眾號(hào)。
當(dāng)進(jìn)程要獲取某些資源(例如從網(wǎng)卡讀取數(shù)據(jù))的時(shí)候,但資源并沒有準(zhǔn)備好(例如網(wǎng)卡還沒接收到數(shù)據(jù)),這時(shí)候內(nèi)核必須切換到其他進(jìn)程運(yùn)行,直到資源準(zhǔn)備好再喚醒進(jìn)程。
waitqueue (等待隊(duì)列) 就是內(nèi)核用于管理等待資源的進(jìn)程,當(dāng)某個(gè)進(jìn)程獲取的資源沒有準(zhǔn)備好的時(shí)候,可以通過調(diào)用 add_wait_queue() 函數(shù)把進(jìn)程添加到 waitqueue 中,然后切換到其他進(jìn)程繼續(xù)執(zhí)行。當(dāng)資源準(zhǔn)備好,由資源提供方通過調(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)程列表。
可以通過調(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);
- }
初始化過程很簡(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)程。
可以通過調(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;
- }
也可以通過調(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)變量后,可以通過調(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)單,首先通過調(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)系如下圖:
waitqueue
休眠等待進(jìn)程
當(dāng)把進(jìn)程添加到等待隊(duì)列后,就可以休眠當(dāng)前進(jìn)程,讓出CPU給其他進(jìn)程運(yùn)行,要休眠進(jìn)程可以通過一下方式:
- 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)程,可以通過 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)喚醒它們。