源码基于: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.hstruct 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.cvoid __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.cvoid 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.hstatic 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.hstatic 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.hstatic 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(),注意函数名是三个下划线:
#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.clong 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.hstatic inline int signal_pending_state(long state, struct task_struct *p)
{//是否允许信号处理,不允许返回0if (!(state & (TASK_INTERRUPTIBLE | TASK_WAKEKILL)))return 0;//当允许信号处理是,确定当前进程是否有信号挂起,如果没有返回0if (!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()
- schedule_timeout()
- freezable_schedule()
- cmd1; schedule(); cmd2
2.5.4 finish_wait()
当退出等待事件需要退出时,在 ___wait_event() 的最后会调用 finish_wait() 函数:
kernel/sched/wait.cvoid 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.cvoid __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.cstatic 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,并初重新初始化bootmarkif (bookmark && (bookmark->flags & WQ_FLAG_BOOKMARK)) {curr = list_next_entry(bookmark, entry);list_del(&bookmark->entry);bookmark->flags = 0;} else //如果bookmark中没有entry,则获取等待队列的第一个entrycurr = list_first_entry(&wq_head->head, wait_queue_entry_t, entry);//确定等待队列是否为空if (&curr->entry == &wq_head->head)return nr_exclusive;//从第一个entry,即curr 开始依次处理等待队列的entrylist_for_each_entry_safe_from(curr, next, &wq_head->head, entry) {unsigned flags = curr->flags;int ret;//跳过等待队列上标记 WQ_FLAG_BOOKMARK的entry,目前系统中没有主动标记的entryif (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_upif (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.cint 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.cvoid
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.cbool
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.cstatic 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() 处理。
最后,附上等待队列的完整流程图: