Linux 中的 waitqueue 机制详解

源码基于:Linux5.10

0. 前言

等待队列(waitqueue) 这个机制在Linux 内核中使用的频率很高,与进程调度机制紧密相关联,可以用来同步对系统资源的访问、异步事件通知、跨进程通信等。网上关于等待队列使用的优秀文章也很多,之所以笔者也写一篇,一是想更新下最新代码中的使用,二是融入些自己的拙见,方便自己回头查看,也希望能有助于后来读者。

1. 原理

引用网上一个专家的一句话:

A "wait queue" in the Linux kernel is a data structure to manage threads that are waiting for some condition to become true;

笔者个人觉得还是这句话概括的挺好。等待队列就是一个数据结构,该数据结构用来管理那些正在等待某个condition 变成true 的进程。

等待队列用 wait_queue_head_t 这个数据结构串联、管理所有正在等待的进程,每一次等待都会创建一个 wait_queue_entry_t,用该数据结构来更细致管理每个进程:

  • private:存放每个正在等待的进程 task_struct 的结构体指针;
  • flags:用以指定该进程处于什么等待属性;
  • func:当该进程被唤醒时,执行一个特殊的回调函数;

本文将结合该图,细致剖析:

  • 等待队列的创建过程;
  • 每个进程的等待过程;
  • 等待队列中每个等待的唤醒过程;

1. waitqueue的基本概念

1.1 waitqueue 的数据结构

include/linux/wait.h

struct wait_queue_entry {
	unsigned int		flags;        //该entry的属性
	void			*private;         //该entry与进程绑定,存放该进程的task_struct指针
	wait_queue_func_t	func;         //回调函数,当等待队列被唤醒时的回调函数
	struct list_head	entry;
};

struct wait_queue_head {
	spinlock_t		lock;             //等待队列的自旋锁,用以同步整个等待队列
	struct list_head	head;         //等待队列的头,用以串联整个等待队列
};
typedef struct wait_queue_head wait_queue_head_t;
typedef struct wait_queue_entry wait_queue_entry_t;

下面来看下等待队列 entry 的flags:

include/linux/wait.h

#define WQ_FLAG_EXCLUSIVE	0x01
#define WQ_FLAG_WOKEN		0x02
#define WQ_FLAG_BOOKMARK	0x04
#define WQ_FLAG_CUSTOM		0x08
#define WQ_FLAG_DONE		0x10

下面来看下等待队列 entry 的回调函数:

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

第一行,指定等待队列回调函数的函数指针类型 wait_queue_func_t;

第二行,指定系统默认的唤醒回调函数 default_wake_function();

1.2 等待队列的创建和初始化

include/linux/wait.h

#define init_waitqueue_head(wq_head)						\
	do {									\
		static struct lock_class_key __key;				\
										\
		__init_waitqueue_head((wq_head), #wq_head, &__key);		\
	} while (0)
kernel/sched/wait.c

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);
}
EXPORT_SYMBOL(__init_waitqueue_head);

等待队列的名称为:传入 init_waitqueue_head() 的变量名 (字符串化);

另外,也可以使用宏 DECLARE_WAIT_QUEUE_HEAD() 进行创建和初始化:

include/linux/wait.h

#define __WAIT_QUEUE_HEAD_INITIALIZER(name) {					\
	.lock		= __SPIN_LOCK_UNLOCKED(name.lock),			\
	.head		= { &(name).head, &(name).head } }

#define DECLARE_WAIT_QUEUE_HEAD(name) \
	struct wait_queue_head name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

1.3 等待队列 entry 的创建和初始化

1.3.1 使用DECLARE_WAITQUEUE()创建

include/linux/wait.h

#define __WAITQUEUE_INITIALIZER(name, tsk) {					\
	.private	= tsk,							\
	.func		= default_wake_function,				\
	.entry		= { NULL, NULL } }

#define DECLARE_WAITQUEUE(name, tsk)						\
	struct wait_queue_entry name = __WAITQUEUE_INITIALIZER(name, tsk)

注意,此种创建的进程是传入的 tsk,另外,唤醒时的回调函数是default_wake_function()

1.3.2 使用DEFINE_WAIT() 创建

include/linux/wait.h

#define DEFINE_WAIT_FUNC(name, function)					\
	struct wait_queue_entry name = {					\
		.private	= current,					\
		.func		= function,					\
		.entry		= LIST_HEAD_INIT((name).entry),			\
	}

#define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)

注意,此种创建的进程是当前进程,另外,唤醒时的回调函数是autoremove_wake_function() 

1.3.3 使用init_wait() 初始化

include/linux/wait.h

#define init_wait(wait)								\
	do {									\
		(wait)->private = current;					\
		(wait)->func = autoremove_wake_function;			\
		INIT_LIST_HEAD(&(wait)->entry);					\
		(wait)->flags = 0;						\
	} while (0)

也可以init_wait() 对定义好的 wait_queue_entry_t 指针 进行初始化。

该entry 绑定的是当前进程,另外,唤醒时的回调函数是autoremove_wake_function() 

1.3.4 在___wait_event()时使用init_wait_entry() 初始化

kernel/sched/wait.c

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);
}
EXPORT_SYMBOL(init_wait_entry);

与 init_wait() 区别是多了一个 flags 参数。

详细的可以查看下文的 __wait_event() 函数和wake_up() 函数。

1.4 添加和移除等待队列

1.4.1 添加到等待队列 

include/linux/wait.h

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


static inline void
__add_wait_queue_exclusive(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
{
	wq_entry->flags |= WQ_FLAG_EXCLUSIVE;
	__add_wait_queue(wq_head, wq_entry);
}

前插到等待队列中,__add_wait_queue_exclusive() 将要插入的entry 指定为 EXCLUSIVE 属性。

include/linux/wait.h

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

static inline void
__add_wait_queue_entry_tail_exclusive(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
{
	wq_entry->flags |= WQ_FLAG_EXCLUSIVE;
	__add_wait_queue_entry_tail(wq_head, wq_entry);
}

后插到等待队列中,__add_wait_queue_entry_tail_exclusive() 将要插入的entry 指定为 EXCLUSIVE 属性。

注意:

对于使用 ___wait_event() 创建的 entry,如果是 EXCLUSIVE 属性,该entry 会被插入到等待队列的尾部,其他属性的entry 将选择前插。

1.4.2 等待队列的移除

include/linux/wait.h

static inline void
__remove_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
{
	list_del(&wq_entry->entry);
}

直接就是调用 list_del() 将指定的 entry 从等待队列中移除。

2. wait event

在上一节中已经讲述了等待队列的基本概念,包括wait_queue_head 和 wait_queue_entry 的创建和初始化。

在初始化结束,会在某个进程中指定一个等待事件,用以阻塞等待唤醒,当 condition 成立时退出此次等待事件。

等待事件的接口有很多,我们接下来分批来看。

2.1 UNINTERRUPTIBLE 等待事件

2.1.1 __wait_event()

#define __wait_event(wq_head, condition)					\
	(void)___wait_event(wq_head, condition, TASK_UNINTERRUPTIBLE, 0, 0,	\
			    schedule())

2.1.2 __io_wait_event()

#define __io_wait_event(wq_head, condition)					\
	(void)___wait_event(wq_head, condition, TASK_UNINTERRUPTIBLE, 0, 0,	\
			    io_schedule())

2.1.3 __wait_event_timeout()

#define __wait_event_timeout(wq_head, condition, timeout)			\
	___wait_event(wq_head, ___wait_cond_timeout(condition),			\
		      TASK_UNINTERRUPTIBLE, 0, timeout,				\
		      __ret = schedule_timeout(__ret))

2.1.4 __wait_event_exclusive_cmd()

#define __wait_event_exclusive_cmd(wq_head, condition, cmd1, cmd2)		\
	(void)___wait_event(wq_head, condition, TASK_UNINTERRUPTIBLE, 1, 0,	\
			    cmd1; schedule(); cmd2)

2.1.5 __wait_event_cmd()

#define __wait_event_cmd(wq_head, condition, cmd1, cmd2)			\
	(void)___wait_event(wq_head, condition, TASK_UNINTERRUPTIBLE, 0, 0,	\
			    cmd1; schedule(); cmd2)

2.1.6 __wait_event_lock_irq()

#define __wait_event_lock_irq(wq_head, condition, lock, cmd)			\
	(void)___wait_event(wq_head, condition, TASK_UNINTERRUPTIBLE, 0, 0,	\
			    spin_unlock_irq(&lock);				\
			    cmd;						\
			    schedule();						\
			    spin_lock_irq(&lock))

注意:

除了 __ion_wait_event() 这个等待事件使用的是 io_schedule(),其他等待事件都是使用 schedule() 函数使得当前进程让出调度,进入休眠状态。

2.2 INTERRUPTIBLE 等待事件

2.2.1 __wait_event_freezable()

#define __wait_event_freezable(wq_head, condition)				\
	___wait_event(wq_head, condition, TASK_INTERRUPTIBLE, 0, 0,		\
			    freezable_schedule())

2.2.2 __wait_event_freezable_timeout()

#define __wait_event_freezable_timeout(wq_head, condition, timeout)		\
	___wait_event(wq_head, ___wait_cond_timeout(condition),			\
		      TASK_INTERRUPTIBLE, 0, timeout,				\
		      __ret = freezable_schedule_timeout(__ret))

2.2.3 __wait_event_freezable_exclusive()

#define __wait_event_freezable_exclusive(wq, condition)				\
	___wait_event(wq, condition, TASK_INTERRUPTIBLE, 1, 0,			\
			freezable_schedule())

2.2.4 __wait_event_interruptible()

#define __wait_event_interruptible(wq_head, condition)				\
	___wait_event(wq_head, condition, TASK_INTERRUPTIBLE, 0, 0,		\
		      schedule())

2.2.5 __wait_event_interruptible_timeout()

#define __wait_event_interruptible_timeout(wq_head, condition, timeout)		\
	___wait_event(wq_head, ___wait_cond_timeout(condition),			\
		      TASK_INTERRUPTIBLE, 0, timeout,				\
		      __ret = schedule_timeout(__ret))

2.2.6 __wait_event_interruptible_exclusive()

#define __wait_event_interruptible_exclusive(wq, condition)			\
	___wait_event(wq, condition, TASK_INTERRUPTIBLE, 1, 0,			\
		      schedule())

2.2.7 __wait_event_interruptible_lock_irq()

#define __wait_event_interruptible_lock_irq(wq_head, condition, lock, cmd)	\
	___wait_event(wq_head, condition, TASK_INTERRUPTIBLE, 0, 0,		\
		      spin_unlock_irq(&lock);					\
		      cmd;							\
		      schedule();						\
		      spin_lock_irq(&lock))

2.3 KILLABLE 等待事件

2.3.1 __wait_event_killable()

#define __wait_event_killable(wq, condition)					\
	___wait_event(wq, condition, TASK_KILLABLE, 0, 0, schedule())

2.3.2 __wait_event_killable_exclusive()

#define __wait_event_killable_exclusive(wq, condition)				\
	___wait_event(wq, condition, TASK_KILLABLE, 1, 0,			\
		      schedule())

2.3.3 __wait_event_killable_timeout()

#define __wait_event_killable_timeout(wq_head, condition, timeout)		\
	___wait_event(wq_head, ___wait_cond_timeout(condition),			\
		      TASK_KILLABLE, 0, timeout,				\
		      __ret = schedule_timeout(__ret))

2.4 IDLE 等待事件

2.4.1 wait_event_idle()

#define wait_event_idle(wq_head, condition)					\
do {										\
	might_sleep();								\
	if (!(condition))							\
		___wait_event(wq_head, condition, TASK_IDLE, 0, 0, schedule());	\
} while (0)

2.4.2 wait_event_idle_exclusive()

#define wait_event_idle_exclusive(wq_head, condition)				\
do {										\
	might_sleep();								\
	if (!(condition))							\
		___wait_event(wq_head, condition, TASK_IDLE, 1, 0, schedule());	\
} while (0)

2.4.3 __wait_event_idle_timeout()

#define __wait_event_idle_timeout(wq_head, condition, timeout)			\
	___wait_event(wq_head, ___wait_cond_timeout(condition),			\
		      TASK_IDLE, 0, timeout,					\
		      __ret = schedule_timeout(__ret))

2.4.4 __wait_event_idle_exclusive_timeout()

#define __wait_event_idle_exclusive_timeout(wq_head, condition, timeout)	\
	___wait_event(wq_head, ___wait_cond_timeout(condition),			\
		      TASK_IDLE, 1, timeout,					\
		      __ret = schedule_timeout(__ret))

2.5 ___wait_event()

对于不同状态的等待事件上面已经列举了,最终调用的都是 ___wait_event(),注意函数名是三个下划线:

include/linux/wait.h

#define ___wait_event(wq_head, condition, state, exclusive, ret, cmd)		\
({										\
	__label__ __out;							\
	struct wait_queue_entry __wq_entry;					\
	long __ret = ret;	/* explicit shadow */				\
										\
	init_wait_entry(&__wq_entry, exclusive ? WQ_FLAG_EXCLUSIVE : 0);	\
	for (;;) {								\
		long __int = prepare_to_wait_event(&wq_head, &__wq_entry, state);\
										\
		if (condition)							\
			break;							\
										\
		if (___wait_is_interruptible(state) && __int) {			\
			__ret = __int;						\
			goto __out;						\
		}								\
										\
		cmd;								\
	}									\
	finish_wait(&wq_head, &__wq_entry);					\
__out:	__ret;									\
})

参数:

  • wq_head:wait_queue_head_t 变量,为什么不使用指针呢? 
  • condition:退出等待的条件,只有为true 时才能退出等待 (__wait_event 函数是个死循环);
  • state:用以等待的 task 状态;
  • exclusive:标记是否是独占等待;
  • ret:一般用于有 timeout时,会将其临时存放在 __ret 中,在cmd 中作为参数带入;
  • cmd:一般是 schedule() 函数或者 schedule_timeout() 函数;

注意其中的 condition 参数,当等待事件中存在 timeout 时,该参数一般会是 ___wait_cond_timeout(condition)

#define ___wait_cond_timeout(condition)						\
({										\
	bool __cond = (condition);						\
	if (__cond && !__ret)							\
		__ret = 1;							\
	__cond || !__ret;							\
})

即,当condition 为true 或者是 timeout 到时了,___wait_event() 的condition 才为 true。

另外,___wait_event() 是个死循环,只有当condition 为true,或者当前进程收到 SIGKILL 信号时,才会退出该死循环。

2.5.1 init_wait_entry()

struct wait_queue_entry __wq_entry;	

init_wait_entry(&__wq_entry, exclusive ? WQ_FLAG_EXCLUSIVE : 0);

当调用 ___wait_event() 时,会创建一个 wait_queue_entry 变量,并调用 init_wait_entry() 对其进行初始化,其中第二个参数 flags 根据 ___wait_event() 的入参 exclusive 决定;

2.5.2 prepare_to_wait_event()

kernel/sched/wait.c

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)) {
		/*
		 * Exclusive waiter must not fail if it was selected by wakeup,
		 * it should "consume" the condition we were waiting for.
		 *
		 * The caller will recheck the condition and return success if
		 * we were already woken up, we can not miss the event because
		 * wakeup locks/unlocks the same wq_head->lock.
		 *
		 * But we need to ensure that set-condition + wakeup after that
		 * can't see us, it should wake up another exclusive waiter if
		 * we fail.
		 */
		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;
}
EXPORT_SYMBOL(prepare_to_wait_event);

每次在等待事件进入shedule 之前,会调用 signal_pending_state() 确定进程是否立即返回 RUNNING 状态:

  • 如果不允许信号处理,返回0,表示不需要返回;
  • 如果允许信号处理,但该进程中没有信号等待处理,也表示不需要返回;
  • 除此,如果是 TASK_INTERRUPTIBLE 或当前进程可以处理信号且收到了 SIGKILL,则需要立即返回RUNNING 状态;
include/linux/sched/signal.h

static inline int signal_pending_state(long state, struct task_struct *p)
{
    //是否允许信号处理,不允许返回0
	if (!(state & (TASK_INTERRUPTIBLE | TASK_WAKEKILL)))
		return 0;

    //当允许信号处理时,确定当前进程是否有信号挂起,如果没有返回0
	if (!signal_pending(p))
		return 0;

	return (state & TASK_INTERRUPTIBLE) || __fatal_signal_pending(p);
}

回到 prepare_to_wait_event() 函数,如果 signal_pending_state() 需要进程立即返回 RUNNING 状态,则认为此次等待事件无效,立即退出此次等待。

如果不需要返回 RUNNING,则会将此次的等待队列 entry 添加到等待队列中。不过需要注意的是,对于 exclusive 属性的entry,会将其添加到等待队列的尾部,其他属性的 entry 添加到等待队列的头部。

prepare_to_wait_event() 函数的最后会调用 set_current_state() 将进程状态标记上。

2.5.3 执行 cmd

cmd 是 ___wait_event() 的最后一个参数,可能是:

  • schedule()
  • io_schedule()
  • schedule_timeout()
  • freezable_schedule()
  • freezable_schedule_timeout()
  • cmd1; schedule(); cmd2

详细可以查看 include/linux/wait.h 文件中调用 ___wait_event() 的地方。

2.5.4 finish_wait()

当退出等待事件需要退出时,在 ___wait_event() 的最后会调用 finish_wait() 函数:

kernel/sched/wait.c

void finish_wait(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
{
	unsigned long flags;

	__set_current_state(TASK_RUNNING);
	/*
	 * We can check for list emptiness outside the lock
	 * IFF:
	 *  - we use the "careful" check that verifies both
	 *    the next and prev pointers, so that there cannot
	 *    be any half-pending updates in progress on other
	 *    CPU's that we haven't seen yet (and that might
	 *    still change the stack area.
	 * and
	 *  - all other users take the lock (ie we can only
	 *    have _one_ other CPU that looks at or modifies
	 *    the list).
	 */
	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);
	}
}
EXPORT_SYMBOL(finish_wait);

主要做了两件事情:

  • 将进程状态改成 RUNNING;
  • 确定该 entry是否还在等待队列中,如果还在队列中,将其从等待队列中移除;

2.6 总结

  • 等待队列的wait event 有很多种,按照进程将要进入的状态分为:
    • INTERRUPTIBLE 等待事件;
    • UNINTERRUPTIBLE 等待事件;
    • KILLABLE 等待事件;
    • IDLE 等待事件;
  • 所有等待事件最终会调用 ___wait_event() 函数;
    • 首先会创建一个 entry,并调用 init_wait_entry() 进行初始化;
    • 进入死循环,直到condition 为true 或进程允许信号中断并收到了 SIGKILL 信号;
    • ___wait_event() 中会调用最后一个参数 cmd,通常是利用 schedule() 交出调度后进入休眠;
    • 当被唤醒后会确定当前进程状态或condition,进而判断是否退出死循环;
    • 当___wait_event() 中退出死循环后,会调用 finish_wait() 将进程状态改为 RUNNING,并将此次的 entry 从等待队列中删除;

3. wake up

3.1 wake_up TASK_NORMAL

include/linux/wait.h

#define wake_up(x)			__wake_up(x, TASK_NORMAL, 1, NULL)
#define wake_up_nr(x, nr)		__wake_up(x, TASK_NORMAL, nr, NULL)
#define wake_up_all(x)			__wake_up(x, TASK_NORMAL, 0, NULL)
#define wake_up_locked(x)		__wake_up_locked((x), TASK_NORMAL, 1)
#define wake_up_all_locked(x)		__wake_up_locked((x), TASK_NORMAL, 0)

有三点注意:

  • wake_up 不携带interruptible 的接口,指的是唤醒 TASK_NORMAL 状态的进程,而TASK_NORMAL 表示的是 (TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE);
  • wake_up 最终调用的有两个接口:__wake_up() 和 __wake_up_locked(),区别在于 __wake_up_locked() 的调用是已经在等待队列的自旋锁中了,不需要再加锁,最终实现都需要调用 __wake_up_common() 函数;
  • wake_up 中有个nr 的参数,用以决定唤醒几个等待的 exclusive entry;

3.2 wake_up INTERRUNPTIBLE

include/linux/wait.h

#define wake_up_interruptible(x)	__wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
#define wake_up_interruptible_nr(x, nr)	__wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
#define wake_up_interruptible_all(x)	__wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
#define wake_up_interruptible_sync(x)	__wake_up_sync((x), TASK_INTERRUPTIBLE)

3.3 __wake_up()

kernel/sched/wait.c

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);
}
EXPORT_SYMBOL(__wake_up);

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

	do {
		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);
}

函数流程比较清晰:

  • 定义了一个 bootmark,用以串联还需要再处理的 entry;
  • 调用 __wake_up_common() 进行实际的唤醒操作;

注意:

这里引入了一个 bookmark,是为了 __wake_up_common() 进去后唤醒的 entry 过多而导致自旋锁持有时间太长。当唤醒的 entry 超过 WAITQUEUE_WALK_BREAK_CNT (默认64) 时,将后面的 entry 放到bookmark 尾部并退出 __wake_up_common() 函数。通过这种机制,实现了进程分批次唤醒,避免了等待队列中自旋锁被持有时间过长

下面单独来看下 __wake_up_common() 的处理:

3.4 __wake_up_common()

kernel/sched/wait.c

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

    //如果是再处理的entry,找到第一个entry,并初重新初始化bootmark
	if (bookmark && (bookmark->flags & WQ_FLAG_BOOKMARK)) {
		curr = list_next_entry(bookmark, entry);

		list_del(&bookmark->entry);
		bookmark->flags = 0;
	} else //如果bookmark中没有entry,则获取等待队列的第一个entry
		curr = list_first_entry(&wq_head->head, wait_queue_entry_t, entry);

    //确定等待队列是否为空
	if (&curr->entry == &wq_head->head)
		return nr_exclusive;

    //从第一个entry,即curr 开始依次处理等待队列的entry
	list_for_each_entry_safe_from(curr, next, &wq_head->head, entry) {
		unsigned flags = curr->flags;
		int ret;

        //跳过等待队列上标记 WQ_FLAG_BOOKMARK的entry,目前系统中没有主动标记的entry
		if (flags & WQ_FLAG_BOOKMARK)
			continue;

        /**
          * 调用等待队列entry绑定的唤醒回调函数
          * 具体唤醒什么样的进程,是INTERRUPTIBLE / UNINTERRUPTIBLE,需要根据mode 决定,
          *   如果需要唤醒的进程mode与当前进程的状态state 不符合,则返回0,
          *   详细查看 try_to_wake_up()函数;
          */
		ret = curr->func(curr, mode, wake_flags, key);
		if (ret < 0)
			break;

        //如果成功唤醒,且当前entry标记了WQ_FLAG_EXCLUSIVE,则nr_exclusive计数要减1
        //  当nr_exclusive 个entry都唤醒了,就不再接着去唤醒了,退出此次wake_up
		if (ret && (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
			break;

        /**
          * 之所以引入临时的bootmark,就为了性能考虑
          * 当连续唤醒的entry 超过了WAITQUEUE_WALK_BREAK_CNT(系统默认为64)时,
          *   会将剩下的entry移入到bootmark之后,返回到上一级函数
          * 通过这种机制,实现了进程分批次唤醒,避免了等待队列中自旋锁被持有时间过长
          */
		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;
}

参数:

  • wq_head:等待队列的头;
  • mode:唤醒进程的状态,TASK_NORMAL 或 TASK_INTERRUPTIBLE;
  • nr_exclusive:指定需要唤醒的 exclusive entry 数量;
  • wake_flags:只有在 __wake_up_sync_key() 函数调用时会传入 WF_SYNC,其他默认都为 0;
  • bootmark:用以标记再处理的 entry;

3.5 总结

wake up 的接口比较多,注意 __wake_up() 和 __wake_up_locked() 的区别在于后者已经处于等待队列的自旋锁中。另外 __wake_up_common_lock() 函数中多了 bookmark机制,实现进程的分批次唤醒。

wake up 最终都是通过唤醒回调函数达到唤醒等待队列的目的,系统默认的回调函数最终都是通过 try_to_wake_up() 来实现。

下面单独来分析唤醒回调函数。

4. 唤醒回调函数

上面 wake_up() 函数中没轮询一个 entry 时,都会调用该entry 的唤醒回调函数。

在 entry 上文第 1.3 节中提到系统默认有两种方式:

  • default_wake_function
  • autoremove_wake_function

唯一区别在于 autoremove_wake_function() 函数在调用 default_wake_function() 之后会将该 entry 从等待队列中移除,如下:

4.1 autoremove_wake_function()

kernel/sched/wait.c

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_careful(&wq_entry->entry);

	return ret;
}
EXPORT_SYMBOL(autoremove_wake_function);

5. 另外一种休眠1

上文第 2、3、4节 基本上剖析的是系统 ___wait_event() 和 wake_up() 方式的休眠和唤醒。

而在第 1.3 节中我们还看到了其他中等待队列 entry 的创建和初始化方式,例如:

include/linux/wait.h

#define DEFINE_WAIT_FUNC(name, function)					\
	struct wait_queue_entry name = {					\
		.private	= current,					\
		.func		= function,					\
		.entry		= LIST_HEAD_INIT((name).entry),			\
	}

#define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)

这种有模块自己定义的 entry,如何使用呢?

系统提供了另外一个函数 prepare_to_wait() 和 prepare_to_wait_exclusive()。

5.1 prepare_to_wait()

kernel/sched/wait.c

void
prepare_to_wait(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry, int state)
{
	unsigned long flags;

	wq_entry->flags &= ~WQ_FLAG_EXCLUSIVE;
	spin_lock_irqsave(&wq_head->lock, flags);
	if (list_empty(&wq_entry->entry))
		__add_wait_queue(wq_head, wq_entry);
	set_current_state(state);
	spin_unlock_irqrestore(&wq_head->lock, flags);
}
EXPORT_SYMBOL(prepare_to_wait);

将该 entry 添加到等待队列的头部,并调用 set_current_state() 将当前进程状态置上。

5.2 prepare_to_wait_exclusive()

kernel/sched/wait.c

bool
prepare_to_wait_exclusive(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry, int state)
{
	unsigned long flags;
	bool was_empty = false;

	wq_entry->flags |= WQ_FLAG_EXCLUSIVE;
	spin_lock_irqsave(&wq_head->lock, flags);
	if (list_empty(&wq_entry->entry)) {
		was_empty = list_empty(&wq_head->head);
		__add_wait_queue_entry_tail(wq_head, wq_entry);
	}
	set_current_state(state);
	spin_unlock_irqrestore(&wq_head->lock, flags);
	return was_empty;
}
EXPORT_SYMBOL(prepare_to_wait_exclusive);

与 prepare_to_wait() 不同的是,这里指定的 entry 是 WQ_FLAG_EXCLUSIVE,并且该entry 是添加到等待队列尾部

5.3 示例

了解完 DEFINE_WAIT()、prepare_to_wait() 之后,模块上可以使用如下示例进行管理:

net/atm/svc.c

static void svc_disconnect(struct atm_vcc *vcc)
{
	DEFINE_WAIT(wait);
    ...
	if (test_bit(ATM_VF_REGIS, &vcc->flags)) {
		sigd_enq(vcc, as_close, NULL, NULL, NULL);
		for (;;) {
			prepare_to_wait(sk_sleep(sk), &wait, TASK_UNINTERRUPTIBLE);
			if (test_bit(ATM_VF_RELEASED, &vcc->flags) || !sigd)
				break;
			schedule();
		}
		finish_wait(sk_sleep(sk), &wait);
	}

    ...

6. 另外一种休眠2

DECLARE_WAIT_QUEUE_HEAD(queue);
DECLARE_WAITQUEUE(wait, current);

for (;;) {
    add_wait_queue(&queue, &wait);
    set_current_state(TASK_INTERRUPTIBLE);
    if (condition)
        break;
    schedule();
    remove_wait_queue(&queue, &wait);
    if (signal_pending(current))
        return -ERESTARTSYS;
}
set_current_state(TASK_RUNNING);
remove_wait_queue(&queue, &wait);

这种方式是手动调用等待队列的add、remove 接口。

7. 总结

至此,等待队列基本剖析完成。其实,根本上就是通过一个数据接口 wait_queue_head_t 串联很多 wait_queue_entry_t,主要在这些 entry 的创建或初始化上,因为此时会指定flags、func 以及对应进程的 private 变量。

  • 在不同的进程中会调用 ___wait_event() 可以自动创建等待队列 entry;
  • 也可以通过 DEFINE_WAIT()、DECLARE_WAITQUEUE() 等方式手动创建等待队列entry;

不同的实现方式对应着不同的唤醒方式:

  • ___wait_event() 中必须要指定 condition,只有在condition 为true或者进程挂起信号并收到信号,才能退出等待,进程才能进入之后的 RUNNING 状态;
  • 通过 DEFINE_WAIT() 等方式,可以配合使用prepare_to_wait(),进而由模块自己组织代码,控制schedule 的时机和唤醒后的条件处理;

当然,也可以不适用等待队列,也能达到休眠、唤醒的目的。都是根据不同的实际需求,进而选择合适的方式。

另外,wait event 的state 有下面几种方式:

  • TASK_UNINTERRUPTIBLE,表示该等待事件只能调用 wake_up 唤醒;
  • TASK_INTERRUPTIBLE,表示该等待事件可以使用wake_up 唤醒,也可以使用信号唤醒;
  • TASK_KILLABLE,表示该等待事件只能由 SIGKILL 唤醒或wake_up 唤醒;
  • TASK_IDLE,表示等待事件只能由wake_up唤醒;

当然,这些state 还组合了 timeout 机制,详细可以查看 schedule_timeout() 处理。

最后,附上等待队列的完整流程图:

### 回答1: Linux waitqueue 是一种 Linux 内核的同步机制,它用于控制进程的执行顺序,使进程之间能够协调进行。 它通过让一个进程在等待另一个进程完成某个操作时进入睡眠状态,并在另一个进程完成操作后唤醒等待的进程。这样,它可以避免进程在不同步的情况下同时进行某些操作,从而减少系统资源的浪费。 因此,waitqueue 机制Linux 内核常用的一种机制,它可以保证系统的正确性和高效性。 ### 回答2: 在Linux操作系统waitqueue是一种用于进程或线程等待的机制。 当一个进程或线程需要等待某个条件满足时,它可以使用waitqueue机制来挂起自己的执行。等待队列(waitqueue)是一个数据结构,用于维护等待某个事件发生的进程或线程的列表。 当一个条件被满足时,比如某个共享资源变为可用,就会唤醒等待该条件的进程或线程。唤醒的过程是通过使用wake_up函数来实现的。 当一个进程或线程需要等待条件满足时,它会调用wait_event函数,将自己加入到等待队列,并将自己标记为等待状态。之后,该进程或线程就会进入睡眠状态,并且由调度器决定运行其他进程或线程。 当条件满足时,比如共享资源变为可用,唤醒该条件的进程或线程的时候,会调用wake_up函数来唤醒等待的进程或线程。被唤醒的进程或线程会从wait_event的调用处继续执行,并继续执行后续逻辑。 需要注意的是,使用waitqueue机制需要配合锁机制使用,以避免竞态条件的产生。在加入等待队列和唤醒过程,需要对共享资源进行加锁保护,以防止并发访问导致的数据不一致性。 总之,waitqueueLinux一种用于进程或线程等待的机制,它通过等待队列来管理等待某个条件满足的进程或线程,并通过唤醒函数来唤醒等待的进程或线程。它是实现同步和互斥的重要工具之一,能够实现进程或线程之间的协作与同步。 ### 回答3: Linuxwaitqueue(等待队列)是一种用于进程调度的机制。它允许一个或多个进程阻塞并等待某个特定条件的满足。 waitqueue是一个数据结构,类似于一个队列,用于存储等待某个条件满足的进程。当一个进程等待某个条件时,它会将自己添加到waitqueue,并进入睡眠状态。 在Linux内核waitqueue通常与锁(如spinlock或mutex)结合使用。当一个进程需要等待某个条件时,它需要先获取锁,在锁的保护下将自己添加到waitqueue,然后释放锁并进入睡眠状态。当条件满足时,另一个进程会获取相同的锁,唤醒等待在waitqueue的进程。 waitqueue实现依赖于内核调度器。当一个进程被唤醒时,它会从睡眠状态返回到可运行状态,并进入内核调度器的调度队列,等待分配CPU执行。 waitqueue提供了一种线程同步的机制,使得进程可以等待某个条件满足而不需要忙等待。它在很多Linux内核的子系统广泛使用,如设备驱动、文件系统等。 总结来说,waitqueueLinux内核用于进程调度的一种机制,它允许一个或多个进程等待某个条件的满足。它借助锁和睡眠状态实现进程的阻塞和唤醒,依赖于内核调度器进行进程的调度。waitqueue在提供进程同步、避免忙等待等方面发挥了重要作用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

私房菜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值