一、wait唤醒步骤
1. 定义并初始化等待队列头 wait_queue_head
struct wait_queue_head { //include/linux/wait.h spinlock_t lock; struct list_head head; }; typedef struct wait_queue_head wait_queue_head_t; //示例: static wait_queue_head_t wait_head; init_waitqueue_head(&wait_head); //初始化 wait_head->head 链表
2. 定义并初始化 wait_queue_entry
struct wait_queue_entry { //include/linux/wait.h unsigned int flags; void *private; wait_queue_func_t func; struct list_head entry; }; //有定义好的宏: #define DEFINE_WAIT_FUNC(name, function) \ struct wait_queue_entry name = { \ .private = current, \ .func = function, \ .entry = LIST_HEAD_INIT((name).entry), \ } #define init_wait(wait) \ do { \ (wait)->private = current; \ (wait)->func = autoremove_wake_function; \ INIT_LIST_HEAD(&(wait)->entry); \ (wait)->flags = 0; \ } while (0) #define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function) //示例: DEFINE_WAIT(wait);
autoremove_wake_function() 就是实际执行唤醒本线程的回调函数,其支持的唤醒mode是通过参数传入的,也就是说由显示调用的唤醒函数决定的。如下:
__sched int autoremove_wake_function(struct wait_queue_entry *wq_entry, unsigned int mode, int sync, void *key) //kernel/sched/wait.c { //调用默认的任务唤醒函数。 int ret = default_wake_function(wq_entry, mode, sync, key); //上面函数成功执行了唤醒动作返回1,没有执行唤醒逻辑返回0。 if (ret) //将此 wait_queue_entry 结构从等待队列头上删除,并重新init删除后的entry list_del_init_careful(&wq_entry->entry); return ret; } EXPORT_SYMBOL(autoremove_wake_function); int default_wake_function(wait_queue_entry_t *curr, unsigned mode, int wake_flags, void *key) //kernel/sched/core.c { WARN_ON_ONCE(IS_ENABLED(CONFIG_SCHED_DEBUG) && wake_flags & ~(WF_SYNC | WF_ANDROID_VENDOR)); //成功执行了唤醒动作返回1,没有执行唤醒逻辑返回0 return try_to_wake_up(curr->private, mode, wake_flags); } EXPORT_SYMBOL(default_wake_function);
3. 准备休眠
void prepare_to_wait(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry, int state) //kernel/sched/wait.c { unsigned long flags; wq_entry->flags &= ~WQ_FLAG_EXCLUSIVE; spin_lock_irqsave(&wq_head->lock, flags); //将wait entry挂到wait head的链表上 if (list_empty(&wq_entry->entry)) __add_wait_queue(wq_head, wq_entry); //设置当前进程为sleep或D状态 set_current_state(state); spin_unlock_irqrestore(&wq_head->lock, flags); } EXPORT_SYMBOL(prepare_to_wait); //示例: prepare_to_wait(&wait_head, &wait, TASK_INTERRUPTIBLE);
4. 将当前任务切走,让出CPU,进入等待状态
//示例: schedule();
5. 等待的事件发生后被唤醒
(1) 支持的唤醒函数
#define TASK_NORMAL (TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE) #define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL) //唤醒等待队列头上的一个sleep或D状态的任务 #define wake_up_nr(x, nr) __wake_up(x, TASK_NORMAL, nr, NULL) //唤醒等待队列头上的nr个sleep或D状态的任务 #define wake_up_all(x) __wake_up(x, TASK_NORMAL, 0, NULL) //唤醒等待队列头上的所有的sleep或D状态的任务 #define wake_up_locked(x) __wake_up_locked((x), TASK_NORMAL, 1) //调用前需要提前获取到 x->lock 才行,没有加_locked的宏在函数内会持 x->lock 锁。 #define wake_up_all_locked(x) __wake_up_locked((x), TASK_NORMAL, 0) #define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL) //唤醒等待队列头上的一个sleep状态的任务 #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) //同步唤醒等待队列头上的一个sleep状态的任务
(2) 示例
wake_up_interruptible(&wait_head); //非同步唤醒一个sleep状态的任务
展开为:
void __wake_up(struct wait_queue_head *wq_head, unsigned int mode, int nr_exclusive, void *key) //kernel/sched/wait.c { __wake_up_common_lock(wq_head, mode, nr_exclusive, 0, key); } EXPORT_SYMBOL(__wake_up); /* wake_up_interruptible(x): (x, TASK_INTERRUPTIBLE, 1, 0, NULL)*/ static void __wake_up_common_lock(struct wait_queue_head *wq_head, unsigned int mode, int nr_exclusive, int wake_flags, void *key) //kernel/sched/wait.c { 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并且关着中断调用的(注意使用对wakeup函数,防止这A-A死锁) 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); } 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); //进入这个函数是需要持 wq_head->lock 锁的 if (bookmark && (bookmark->flags & WQ_FLAG_BOOKMARK)) { curr = list_next_entry(bookmark, entry); list_del(&bookmark->entry); bookmark->flags = 0; } else { //从链表首取下一个 wait entry curr = list_first_entry(&wq_head->head, wait_queue_entry_t, entry); } //若是个空的等待队列头就退出,返回要唤醒的线程数 if (&curr->entry == &wq_head->head) return nr_exclusive; //只遍历curr以及curr以后的任务 list_for_each_entry_safe_from(curr, next, &wq_head->head, entry) { //取出 wait_queue_entry 的 flag 成员 unsigned flags = curr->flags; int ret; if (flags & WQ_FLAG_BOOKMARK) continue; //执行 wait_queue_entry 的 func 回调函数来执行真正的唤醒动作!成功执行了唤醒动作返回1,没有执行唤醒动作返回0. ret = curr->func(curr, mode, wake_flags, key); if (ret < 0) break; //若指定了 WQ_FLAG_EXCLUSIVE 标志,且已经唤醒的数量达到了参数nr的数量要求 if (ret && (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive) break; //单次唤醒超过64个就释放暂停一下,以免持有 rq->lock 的时间过长 if (bookmark && (++cnt > WAITQUEUE_WALK_BREAK_CNT) && (&next->entry != &wq_head->head)) { //若是唤醒的数量没有达到要求,就赋上 WQ_FLAG_BOOKMARK 标志,表示下次从这个断点位置重新开始唤醒 bookmark->flags = WQ_FLAG_BOOKMARK; //bookmark是个只起标记作用的dummy的 wait_queue_entry 结构 list_add_tail(&bookmark->entry, &next->entry); break; } } return nr_exclusive; }
示例中的线程唤醒回调函数是 autoremove_wake_function()。
6. 结束等待
void finish_wait(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry) { unsigned long flags; //设置当前进程状态为 running 状态 __set_current_state(TASK_RUNNING); /* * 若wait条目还在等待队列头上挂着,就将其摘取下来。由前面的唤醒回调函数可知, * 对于实际唤醒的entry已经摘下来了,对于p->state判断不满足唤醒匹配条件的还在 * 等待队列头上挂着,在这个时候才摘下来。 */ 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); //示例: finish_wait(&wait_head, &wait);
二、使用Demo举例
1. binder驱动中的一个删减后的例子
struct binder_thread { ... wait_queue_head_t wait; ... }; //休眠等待函数 static int binder_wait_for_work(struct binder_thread *thread, bool do_proc_work) { /* 1. 定义并初始化一个wait entry,指定实际的唤醒回调函数 */ DEFINE_WAIT(wait); int ret = 0; /* * 冻结时跳过此任务,freeze_task()和系统休眠流程中的冻结判断有此标志位的任务 * 就会跳过它,使用 freezer_should_skip()来判断此标志位. */ freezer_do_not_count(); for (;;) { /* 2. 将entry挂入等待队列头中,并将当前线程设置为INTERRUPTIBLE状态,准备休眠 */ prepare_to_wait(&thread->wait, &wait, TASK_INTERRUPTIBLE); /* 3. 休眠循环退出条件 */ if (binder_has_work_ilocked(thread, do_proc_work)) break; /* 4. 将当前任务切走,让出cpu */ schedule(); /* TASK_INTERRUPTIBLE 类型的休眠需要处理是否是信号唤醒 */ if (signal_pending(current)) { ret = -EINTR; break; } } /* * 5. 设置唤醒的任务为running状态(唤醒函数中也会设置,部分Case下重复设置了), * 并将p->state为不支持的唤醒类型的任务从等待队列头链表上删除。 */ finish_wait(&thread->wait, &wait); /* 取消冻结跳过标志位 */ freezer_count(); return ret; } //唤醒 static void binder_wakeup_thread_ilocked(struct binder_proc *proc, struct binder_thread *thread, bool sync) { if (thread) { if (sync) wake_up_interruptible_sync(&thread->wait); else wake_up_interruptible(&thread->wait); return; } }
三。总结
1. 必须要根据wait时指定的休眠类型,来选择唤醒的类型。
2. wake_up_xxx() 函数只是提供了一个唤醒框架,最终还是要调用到wait entry的回调函数执行实际的唤醒,回调函数中调用的 try_to_wake_up() 应该是最终归一的唤醒函数。
3. wake_up_xxx() 函数只是提供了一个唤醒框架中,采用书签这个虚拟的wait entry机制,来限制每次最大唤醒任务数量为64个,以免持有 rq->lock 过长时间。