一、Linux等待队列概述
Linux内核的等待队列(Wait Queue)是重要的数据结构,与进程调度机制紧密相关联,可以用来同步对系统资源的访问、异步事件通知、跨进程通信等。
在Linux中,等待队列以循环链表为基础结构,包括两种数据结构:等待队列头(wait queue head)和等待队列元素(wait queue),整个等待队列由等待队列头进行管理。下文将用内核源码(基于Linux kernel 5.2)对等待队列进行介绍,详细说明采用等待队列实现进程阻塞和唤醒的方法。
二、等待队列头和等待队列元素
等待队列以循环链表为基础结构,链表头和链表项分别为等待队列头和等待队列元素,分别用结构体 wait_queue_head_t 和 wait_queue_entry_t 描述(定义在 linux/wait.h )。
2.1 基本概念
1 2 3 4 5
| struct wait_queue_head { spinlock_t lock; struct list_head head; }; typedef struct wait_queue_head wait_queue_head_t;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| typedef int (*wait_queue_func_t)(struct wait_queue_entry *wq_entry, unsigned mode, int flags, void *key); int default_wake_function(struct wait_queue_entry *wq_entry, unsigned mode, int flags, void *key);
#define WQ_FLAG_EXCLUSIVE 0x01 #define WQ_FLAG_WOKEN 0x02 #define WQ_FLAG_BOOKMARK 0x04
struct wait_queue_entry { unsigned int flags; void *private; wait_queue_func_t func; struct list_head entry; };
typedef struct wait_queue_entry wait_queue_entry_t;
|
等待队列头结构包括一个自旋锁和一个链表头。等待队列元素除了包括链表项,还包括:
- flags : 标识队列元素状态和属性
*private: 用于指向关联进程 task_struct 结构体的指针
- func : 函数指针,用于指向等待队列被唤醒时的回调的唤醒函数
以进程阻塞和唤醒的过程为例,等待队列的使用场景可以简述为:
进程 A 因等待某些资源(依赖进程 B 的某些操作)而不得不进入阻塞状态,便将当前进程加入到等待队列 Q 中。进程 B 在一系列操作后,可通知进程 A 所需资源已到位,便调用唤醒函数 wake up 来唤醒等待队列上 Q 的进程,注意此时所有等待在队列 Q 上的进程均被置为可运行状态。
借助上述描述场景,说明等待队列元素属性 flags 标志的作用,下文也将结合源码进行详细解读。
(1) WQ_FLAG_EXCLUSIVE
上述场景中看到,当某进程调用 wake up 函数唤醒等待队列时,队列上所有的进程均被唤醒,在某些场合会出现唤醒的所有进程中,只有某个进程获得了期望的资源,而其他进程由于资源被占用不得不再次进入休眠。如果等待队列中进程数量庞大时,该行为将影响系统性能。
内核增加了”独占等待”(WQ_FLAG_EXCLUSIVE)来解决此类问题。一个独占等待的行为和通常的休眠类似,但有如下两个重要的不同:
等待队列元素设置 WQ_FLAG_EXCLUSIVE 标志时,会被添加到等待队列的尾部,而非头部。
在某等待队列上调用 wake up 时,执行独占等待的进程每次只会唤醒其中第一个(所有非独占等待进程仍会被同时唤醒)。
(2) WQ_FLAG_WOKEN
暂时还未理解,TODO
(3) WQ_FLAG_BOOKMARK
用于 wake_up() 唤醒等待队列时实现分段遍历,减少单次对自旋锁的占用时间。
三、 等待队列的创建和初始化
等待队列头的定义和初始化有两种方式: init_waitqueue_head(&wq_head) 和宏定义 DECLARE_WAIT_QUEUE_HEAD(name) 。
1 2 3 4 5 6 7 8 9 10 11 12
| #define init_waitqueue_head(wq_head) \ do { \ static struct lock_class_key __key; \ __init_waitqueue_head((wq_head), #wq_head, &__key); \ } while (0)
void __init_waitqueue_head(struct wait_queue_head *wq_head, const char *name, struct lock_class_key *key) { spin_lock_init(&wq_head->lock); lockdep_set_class_and_name(&wq_head->lock, key, name); INIT_LIST_HEAD(&wq_head->head); }
|
1 2 3 4 5 6
| #define DECLARE_WAIT_QUEUE_HEAD(name) \ struct wait_queue_head name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
#define __WAIT_QUEUE_HEAD_INITIALIZER(name) { \ .lock = __SPIN_LOCK_UNLOCKED(name.lock), \ .head = { &(name).head, &(name).head } }
|
四、等待队列元素的创建和初始化
创建等待队列元素较为普遍的一种方式是调用宏定义 DECLARE_WAITQUEUE(name, task) ,将定义一个名为 name 的等待队列元素, private 数据指向给定的关联进程结构体 task ,唤醒函数为 default_wake_function() 。后文介绍唤醒细节时详细介绍唤醒函数的工作。
1 2 3 4 5 6 7
| #define DECLARE_WAITQUEUE(name, tsk) \ struct wait_queue_entry name = __WAITQUEUE_INITIALIZER(name, tsk)
#define __WAITQUEUE_INITIALIZER(name, tsk) { \ .private = tsk, \ .func = default_wake_function, \ .entry = { NULL, NULL } }
|
内核源码中还存在其他定义等待队列元素的方式,调用宏定义 DEFINE_WAIT(name) 和 init_wait(&wait_queue) 。
这两种方式都将当前进程(current)关联到所定义的等待队列上,唤醒函数为 autoremove_wake_function() ,注意此函数与上述宏定义方式时不同(上述定义中使用 default_wake_function() )。
下文也将介绍 DEFINE_WAIT() 和 DECLARE_WAITQUEUE() 在使用场合上的不同。
1 2 3 4 5 6 7
| #define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function) #define DEFINE_WAIT_FUNC(name, function) \ struct wait_queue_entry name = { \ .private = current, \ .func = function, \ .entry = LIST_HEAD_INIT((name).entry), \ }
|
1 2 3 4 5 6 7
| #define init_wait(wait) \ do { \ (wait)->private = current; \ (wait)->func = autoremove_wake_function; \ INIT_LIST_HEAD(&(wait)->entry); \ (wait)->flags = 0; \ } while (0)
|
五、添加和移除等待队列
内核提供了两个函数(定义在 kernel/sched/wait.c )用于将等待队列元素 wq_entry 添加到等待队列 wq_head 中: add_wait_queue() 和 add_wait_queue_exclusive() 。
add_wait_queue() :在等待队列头部添加普通的等待队列元素(非独占等待,清除 WQ_FLAG_EXCLUSIVE 标志)。
add_wait_queue_exclusive() :在等待队列尾部添加独占等待队列元素(设置了 WQ_FLAG_EXCLUSIVE 标志)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry) { unsigned long flags; wq_entry->flags &= ~WQ_FLAG_EXCLUSIVE; spin_lock_irqsave(&wq_head->lock, flags); __add_wait_queue(wq_head, wq_entry); spin_unlock_irqrestore(&wq_head->lock, flags); }
static inline void __add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry) { list_add(&wq_entry->entry, &wq_head->head); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void add_wait_queue_exclusive(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry) { unsigned long flags; wq_entry->flags |= WQ_FLAG_EXCLUSIVE; spin_lock_irqsave(&wq_head->lock, flags); __add_wait_queue_entry_tail(wq_head, wq_entry); spin_unlock_irqrestore(&wq_head->lock, flags); }
static inline void __add_wait_queue_entry_tail(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry) { list_add_tail(&wq_entry->entry, &wq_head->head); }
|
remove_wait_queue() 函数用于将等待队列元素 wq_entry 从等待队列 wq_head 中移除。
1 2 3 4 5 6 7 8 9 10 11 12 13
| void remove_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry) { unsigned long flags; spin_lock_irqsave(&wq_head->lock, flags); __remove_wait_queue(wq_head, wq_entry); spin_unlock_irqrestore(&wq_head->lock, flags); }
static inline void __remove_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry) { list_del(&wq_entry->entry); }
|
添加和移除等待队列的示意图如下所示:

六、等待事件
内核中提供了等待事件 wait_event() 宏(以及它的几个变种),可用于实现简单的进程休眠,等待直至某个条件成立,主要包括如下几个定义:
wait_event(wq_head, condition)
wait_event_timeout(wq_head, condition, timeout)
wait_event_interruptible(wq_head, condition)
wait_event_interruptible_timeout(wq_head, condition, timeout)
io_wait_event(wq_head, condition)
上述所有形式函数中, wq_head 是等待队列头(采用”值传递“的方式传输函数), condition 是任意一个布尔表达式。使用 wait_event ,进程将被置于非中断休眠,而使用 wait_event_interruptible 时,进程可以被信号中断。
另外两个版本 wait_event_timeout 和 wait_event_interruptible_timeout 会使进程只等待限定的时间(以jiffy表示,给定时间到期时,宏均会返回0,而无论 condition 为何值)。
详细介绍 wait_event() 函数的实现原理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
| #define wait_event(wq_head, condition) \ do { \ might_sleep(); \
#define __wait_event(wq_head, condition) \ (void)___wait_event(wq_head, condition, TASK_UNINTERRUPTIBLE, 0, 0, schedule())
#define ___wait_event(wq_head, condition, state, exclusive, ret, cmd) \ ({ \ __label__ __out; \ struct wait_queue_entry __wq_entry; \ long __ret = ret; \ \
void init_wait_entry(struct wait_queue_entry *wq_entry, int flags) { wq_entry->flags = flags; wq_entry->private = current; wq_entry->func = autoremove_wake_function; INIT_LIST_HEAD(&wq_entry->entry); }
long prepare_to_wait_event(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry, int state) { unsigned long flags; long ret = 0; spin_lock_irqsave(&wq_head->lock, flags); if (signal_pending_state(state, current)) { list_del_init(&wq_entry->entry); ret = -ERESTARTSYS; } else { if (list_empty(&wq_entry->entry)) { if (wq_entry->flags & WQ_FLAG_EXCLUSIVE) __add_wait_queue_entry_tail(wq_head, wq_entry); else __add_wait_queue(wq_head, wq_entry); } set_current_state(state); } spin_unlock_irqrestore(&wq_head->lock, flags); return ret; }
#define set_current_state(state_value) \ do { \ WARN_ON_ONCE(is_special_task_state(state_value));\ current->task_state_change = _THIS_IP_; \ smp_store_mb(current->state, (state_value)); \ } while (0)
void finish_wait(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry) { unsigned long flags; __set_current_state(TASK_RUNNING); if (!list_empty_careful(&wq_entry->entry)) { spin_lock_irqsave(&wq_head->lock, flags); list_del_init(&wq_entry->entry); spin_unlock_irqrestore(&wq_head->lock, flags); } }
|
经过源码分析可以看到, wait_event 使进程进入非中断休眠状态,循环等待直至特定条件满足,否则进程继续保持休眠状态。
可以简单总结出使用等待队列使进程休眠的一般步骤:
- 将当前进程关联的等待队列元素加入到等待队列中。 __add_wait_queue()/__add_wait_queue_entry_tail()
- 设置当前进程状态(可中断 TASK_INTERRUPTIBLE 或不可中断 TASK_UNINTERRUPTIBLE)。 set_current_state()
- 判断资源是否得到,或是否捕获中断信号。
- 进程让出调度器,进入休眠状态。 schedule()
- 资源得到满足时,将等待队列元素从等待队列中移除。
七、等待队列唤醒
前文已经简单提到, wake_up 函数可用于将等待队列上的所有进程唤醒,和 wait_event 相对应, wake_up 函数也包括多个变体。主要包括:
wake_up(&wq_head)
wake_up_interruptible(&wq_head)
wake_up_nr(&wq_head, nr)
wake_up_interruptible_nr(&wq_head, nr)
wake_up_interruptible_all(&wq_head)
wake_up()
wake_up() 可以用来唤醒等待队列上的所有进程,而 wake_up_interruptible() 只会唤醒那些执行可中断休眠的进程。因此约定, wait_event() 和 wake_up() 搭配使用,而 wait_event_interruptible() 和 wake_up_interruptible() 搭配使用。
前文提到,对于独占等待的进程, wake_up() 只会唤醒第一个独占等待进程。 wake_up_nr() 函数提供功能,它能唤醒给定数目nr个独占等待进程,而不是只有一个。
wake_up() 函数的实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| #define TASK_NORMAL (TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE)
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL) void __wake_up(struct wait_queue_head *wq_head, unsigned int mode, int nr_exclusive, void *key) { __wake_up_common_lock(wq_head, mode, nr_exclusive, 0, key); }
static void __wake_up_common_lock(struct wait_queue_head *wq_head, unsigned int mode, int nr_exclusive, int wake_flags, void *key) { unsigned long flags; wait_queue_entry_t bookmark; bookmark.flags = 0; bookmark.private = NULL; bookmark.func = NULL; INIT_LIST_HEAD(&bookmark.entry); spin_lock_irqsave(&wq_head->lock, flags); nr_exclusive = __wake_up_common(wq_head, mode, nr_exclusive, wake_flags, key, &bookmark); spin_unlock_irqrestore(&wq_head->lock, flags); while (bookmark.flags & WQ_FLAG_BOOKMARK) { spin_lock_irqsave(&wq_head->lock, flags); nr_exclusive = __wake_up_common(wq_head, mode, nr_exclusive, wake_flags, key, &bookmark); spin_unlock_irqrestore(&wq_head->lock, flags); } }
#define WAITQUEUE_WALK_BREAK_CNT 64 static int __wake_up_common(struct wait_queue_head *wq_head, unsigned int mode, int nr_exclusive, int wake_flags, void *key, wait_queue_entry_t *bookmark) { wait_queue_entry_t *curr, *next; int cnt = 0; lockdep_assert_held(&wq_head->lock); if (bookmark && (bookmark->flags & WQ_FLAG_BOOKMARK)) { curr = list_next_entry(bookmark, entry); list_del(&bookmark->entry); bookmark->flags = 0; } else curr = list_first_entry(&wq_head->head, wait_queue_entry_t, entry); if (&curr->entry == &wq_head->head) return nr_exclusive; list_for_each_entry_safe_from(curr, next, &wq_head->head, entry) { unsigned flags = curr->flags; int ret; if (flags & WQ_FLAG_BOOKMARK) continue; ret = curr->func(curr, mode, wake_flags, key); if (ret < 0) break; if (ret && (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive) break; if (bookmark && (++cnt > WAITQUEUE_WALK_BREAK_CNT) && (&next->entry != &wq_head->head)) { bookmark->flags = WQ_FLAG_BOOKMARK; list_add_tail(&bookmark->entry, &next->entry); break; } } return nr_exclusive; }
|
wake_up() 函数会遍历等待队列上的所有元素(包括TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE)),根据 nr_exclusive 参数的要求唤醒进程,同时实现了分批次唤醒工作。最终会回调等待队列元素所绑定的唤醒函数。
前文已经提到,定义等待队列元素时主要涉及到两种唤醒回调函数:
default_wake_function() :宏定义 DECLARE_WAITQUEUE(name, tsk) 使用的唤醒函数。
autoremove_wake_function() : DEFINE_WAIT(name) , init_wait(wait) 和 wait_event() 中调用的 init_wait_entry() 使用此唤醒函数。
default_wake_function()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| int default_wake_function(wait_queue_entry_t *curr, unsigned mode, int wake_flags, void *key) { return try_to_wake_up(curr->private, mode, wake_flags); }
static int try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags) { unsigned long flags; int cpu, success = 0; raw_spin_lock_irqsave(&p->pi_lock, flags); smp_mb__after_spinlock(); if (!(p->state & state)) goto out; trace_sched_waking(p); success = 1; cpu = task_cpu(p); smp_rmb(); if (p->on_rq && ttwu_remote(p, wake_flags)) goto stat; ... ... ttwu_queue(p, cpu, wake_flags); stat: ttwu_stat(p, cpu, wake_flags); out: raw_spin_unlock_irqrestore(&p->pi_lock, flags); return success; }
static void ttwu_queue(struct task_struct *p, int cpu, int wake_flags) { struct rq *rq = cpu_rq(cpu); struct rq_flags rf; ... ... rq_lock(rq, &rf); update_rq_clock(rq); ttwu_do_activate(rq, p, wake_flags, &rf); rq_unlock(rq, &rf); }
static void ttwu_do_activate(struct rq *rq, struct task_struct *p, int wake_flags, struct rq_flags *rf) { int en_flags = ENQUEUE_WAKEUP | ENQUEUE_NOCLOCK; lockdep_assert_held(&rq->lock); ... ... activate_task(rq, p, en_flags); ttwu_do_wakeup(rq, p, wake_flags, rf); }
static void ttwu_do_wakeup(struct rq *rq, struct task_struct *p, int wake_flags, struct rq_flags *rf) { check_preempt_curr(rq, p, wake_flags); p->state = TASK_RUNNING; trace_sched_wakeup(p); ... ... }
|
从函数调用过程中可以看到, default_wake_function() 实现唤醒进程的过程为:
default_wake_function() –> try_to_wake_up() –> ttwu_queue() –> ttwu_do_activate() –> ttwu_do_wakeup()
值得一提的是, default_wake_function() 的实现中并未将等待队列元素从等待队列中删除。因此,编写程序时不能忘记添加步骤将等待队列元素从等待队列元素中删除。
autoremove_wake_function()
1 2 3 4 5 6 7
| int autoremove_wake_function(struct wait_queue_entry *wq_entry, unsigned mode, int sync, void *key) { int ret = default_wake_function(wq_entry, mode, sync, key); if (ret) list_del_init(&wq_entry->entry); return ret; }
|
autoremove_wake_function() 相比于 default_wake_function() ,在成功执行进程唤醒工作后,会自动将等待队列元素从等待队列中移除。