linux 等待队列
一、等待队列介绍
linux 内核的等待队列和进程调度息息相关,进程在某些情况下必须等待某些事件的发生,例如:等待一个磁盘操作的终止,等待释放系统资源,或等待指定的时间间隔。
等待队列实现了在事件上的条件等待:希望等待特定事件的进程把自己放进合适的等待队列,并放弃控制权。
下面以llinx 4.9为例介绍等待队列,等待队列的定义和接口函数都在头文件linux/include/wait.h中,等待队列的实现在linux/kernel/sched/wait.c中
linux 用wait_queue_t表示等待队列,如下所示
typedef struct __wait_queue wait_queue_t; typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int flags, void *key);/* __wait_queue::flags */ #define WQ_FLAG_EXCLUSIVE 0x01 #define WQ_FLAG_WOKEN 0x02 struct __wait_queue { unsigned int flags; void *private; wait_queue_func_t func; struct list_head task_list; };
用wait_queue_head_t表示等待队列的头部
struct __wait_queue_head { spinlock_t lock; struct list_head task_list; }; typedef struct __wait_queue_head wait_queue_head_t;
二、等待队列定义和接口函数
2.1 定义等待队列和等待队列头部
可以用DECLARE_WAITQUEUE(name, tsk)来定义等待队列,如DECLARE_WAITQUEUE(yy_wait, current)
DECLARE_WAITQUEUE是个宏,最终是调用了宏__WAITQUEUE_INITIALIZER来初始化等待队列接头体中的成员,如下所示:
#define __WAITQUEUE_INITIALIZER(name, tsk) { \ .private = tsk, \ .func = default_wake_function, \ .task_list = { NULL, NULL } } #define DECLARE_WAITQUEUE(name, tsk) \ wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
可以用DECLARE_WAIT_QUEUE_HEAD(name)来初始化一个等待队列头部,如DECLARE_WAIT_QUEUE_HEAD(yy_wait_head)
DECLARE_WAIT_QUEUE_HEAD也是个宏,最终是调用宏__WAIT_QUEUE_HEAD_INITIALIZER来初始化wait_queue_head_t中的成员
#define __WAIT_QUEUE_HEAD_INITIALIZER(name) { \ .lock = __SPIN_LOCK_UNLOCKED(name.lock), \ .task_list = { &(name).task_list, &(name).task_list } } #define DECLARE_WAIT_QUEUE_HEAD(name) \ wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
2.2 等待事件
wait_event(wq, condition) :休眠,直到 condition 为真;退出的唯一条件是 condition 为真,无法被信号打断
wait_event_interruptible(wq, condition) :休眠,直到 condition 为真;休眠期间是可被信号打断
wait_event_timeout(wq, condition, timeout) :与wati_event相比多了一个timeout,表示最多等待timeout时间,以jiffy为单位,超过时间后无论condition是否为真都返回
wait_event_interruptible_timeout(wq, condition, timeout):与wait_event_timeout相比不同的就是休眠期间可被信号打断
2.3 唤醒事件
wake_up(x):唤醒以x为等待队列头部的所有进程,应与wait_event或wait_event_timeout成对使用,wake_up能 唤醒TASK_UNINTERRUPTIBLE和TASK_INTERRUPTIBLE状态的进程
wake_up_interruptible(x):唤醒以x为等待队列头部的所有进程,应与wait_event_interruptible或wait_event_interruptible_timeout成对使用,wake_up_interruptible只能能 唤醒TTASK_INTERRUPTIBLE状态的进程
三、等待事件源码分析
3.1 wait_event源码分析
#define wait_event(wq, condition) \ do { \ might_sleep(); \ \\在非debug模式这个函数是个空函数,可忽略,在debug模式提醒开发人员,调用该函数的函数可能会sleep if (condition) \ \\如果条件真则直接退出等待 break; \ __wait_event(wq, condition); \ } while (0)
#define __wait_event(wq, condition) \ (void)___wait_event(wq, condition, TASK_UNINTERRUPTIBLE, 0, 0, \ schedule())
#define ___wait_event(wq, condition, state, exclusive, ret, cmd) \ ({ \ __label__ __out; \ wait_queue_t __wait; \ long __ret = ret; /* explicit shadow */ \ \ init_wait_entry(&__wait, exclusive ? WQ_FLAG_EXCLUSIVE : 0); \ //初始化__wait结构体 for (;;) { \ long __int = prepare_to_wait_event(&wq, &__wait, state);\ //将等待队列__wait添加到等待队列头部wq中,并设置进程状态为state \ if (condition) \ //如果条件为真则直接结束等待 break; \ \ if (___wait_is_interruptible(state) && __int) { \ __ret = __int; \ goto __out; \ } \ \ cmd; \ //执行__wait_event中传进来的schedule()函数,这个函数是让当前进程进入睡眠状态,调度别的进程 } \ finish_wait(&wq, &__wait); \ //结束等待,设置当前进程状态为TASK_RUNNING,并将__wait从等待队列头部wq中删除 __out: __ret; \ })
long prepare_to_wait_event(wait_queue_head_t *q, wait_queue_t *wait, int state) { unsigned long flags; long ret = 0; spin_lock_irqsave(&q->lock, flags); if (unlikely(signal_pending_state(state, current))) { list_del_init(&wait->task_list); ret = -ERESTARTSYS; } else { if (list_empty(&wait->task_list)) { //如果wait还没有添加到等待队列头部中则执行添加,如果已经添加了就不在重复添加 if (wait->flags & WQ_FLAG_EXCLUSIVE) __add_wait_queue_tail(q, wait); else __add_wait_queue(q, wait); } set_current_state(state); //设置当前进程状态 } spin_unlock_irqrestore(&q->lock, flags); return ret; }
3.2 wait_event_interruptible源码分析
#define __wait_event_interruptible(wq, condition) \ ___wait_event(wq, condition, TASK_INTERRUPTIBLE, 0, 0, \ schedule())
#define wait_event_interruptible(wq, condition) \ ({ \ int __ret = 0; \ might_sleep(); \ if (!(condition)) \ __ret = __wait_event_interruptible(wq, condition); \ __ret; \ })
从wait_event_interruptible源码中可以看出,它与wait_event的不同只是在调用___wait_event时候传入的state参数不一样,wait_event传入的state参数是TASK_UNINTERRUPTIBLE,wait_event_interruptible传入的参数是TASK_INTERRUPTIBLE
四、唤醒事件源码分析
4.1 wake_up源码分析
wake_up定义在wait.h中,如下所示:
wake_up是调用__wake_up,__wake_up定义在linux/kernel/sched/wait.c中,
void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr_exclusive, void *key) { unsigned long flags; spin_lock_irqsave(&q->lock, flags); __wake_up_common(q, mode, nr_exclusive, 0, key); spin_unlock_irqrestore(&q->lock, flags); }
static void __wake_up_common(wait_queue_head_t *q, unsigned int mode, int nr_exclusive, int wake_flags, void *key) { wait_queue_t *curr, *next; list_for_each_entry_safe(curr, next, &q->task_list, task_list) { //遍历等待队列头部q下的所有等待等待队列,并执行每个等待队列中的func函数 unsigned flags = curr->flags; if (curr->func(curr, mode, wake_flags, key) && (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive) break; } }
从源码中可以看到,__wake_up就是加上锁后调用__wake_up_common,__wake_up_common函数就是遍历等待队列头部q下的所有等待等待队列,并执行每个等待队列中的func函数。func函数在2.1节中可以看到是default_wake_function,所以真正唤醒的函数就是这个default_wake_function。这个函数定义在linux/kernel/sched/core.c 中。
default_wake_function调用的是try_to_wake_up, try_to_wake_up函数通过把进程状态设置为TASK_RUNNING,并把该进程插入本地CPU运行队列rq来达到唤醒睡眠和停止的进程的目的。例如:调用该函数唤醒等待队列中的进程,或恢复执行等待信号的进程。
static int try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags) { unsigned long flags; int cpu, success = 0; smp_mb__before_spinlock(); raw_spin_lock_irqsave(&p->pi_lock, flags); if (!(p->state & state)) goto out; trace_sched_waking(p); success = 1; /* we're going to change ->state */ cpu = task_cpu(p); smp_rmb(); if (p->on_rq && ttwu_remote(p, wake_flags)) goto stat; #ifdef CONFIG_SMP smp_rmb(); smp_cond_load_acquire(&p->on_cpu, !VAL); p->sched_contributes_to_load = !!task_contributes_to_load(p); p->state = TASK_WAKING; cpu = select_task_rq(p, p->wake_cpu, SD_BALANCE_WAKE, wake_flags); if (task_cpu(p) != cpu) { wake_flags |= WF_MIGRATED; set_task_cpu(p, cpu); } #endif /* CONFIG_SMP */ ttwu_queue(p, cpu, wake_flags); stat: ttwu_stat(p, cpu, wake_flags); out: raw_spin_unlock_irqrestore(&p->pi_lock, flags); return success; }
4.2 wake_up_interruptible
wake_up_interruptible和wake_up不一样的地方就是传入__wake_up mode参数是TASK_INTERRUPTIBLE
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通