首页 > 编程学习 > wait_queue机制浅析

wait_queue机制浅析

发布时间:2022/8/12 13:35:36

wait_queue机制浅析

在内核中,如果一个任务需要等待一个事件,如何实现当事件未发生时该任务睡眠节省CPU资源,当事件发生时任务及时被唤醒继续工作呢?wait_event/wake_up机制是一个不错的选择。下面这个场景展示了wait_queue的基本用法

taskA schedule_system taskB do something wait_event schedule sleep the event happened wake_up schedule schedule do somthing taskA schedule_system taskB
void taskA(void) {
    /* do_something */
    ....

    /* 如果事件未发生(condition表达式不为真) taskA睡眠*/
    wait_event(wq, condition);
    
    /* do_something */
    ....
}

void taskB(void) {
    /* do_something */
    ....

    /* taskA等待的事件发生,唤醒taskA*/
    wake_up(wq, condition);
    
    /* do_something */
    ....
}

个人理解如果要浅层次的理解wait queue机制,可以简单概括为,在需要等待某个事件时,将任务挂载到等待链表上,然后使该任务随眠,切换至其他任务执行;当事件发生时,遍历该链表上的任务,将其一一从等待链表上摘除唤醒。这是wait queue机制的骨架,至于超时唤醒,条件判断等机制都是在该骨架上衍生而来。

1 数据结构

struct wait_queue_head {
	spinlock_t		lock;
	struct list_head	head;
};

struct wait_queue_entry {
    /* 配置标志 */
	unsigned int		flags;
    /* task_struct */
	void			*private;
    /* 唤醒函数 */
	wait_queue_func_t	func;
	struct list_head	entry;
};

wait_queue_head这个数据结构很简单,就是一个带锁的链表,wait_queue机制用这个链表来代表一个事件,所有等待这个事件的任务都会用wait_queue_entry结构体表示,并挂载到这个链表上

2 wait_event()

#define ___wait_event(wq_head, condition, state, exclusive, ret, cmd)		\
({										\
	__label__ __out;							\
	struct wait_queue_entry __wq_entry;					/* (1) */ \
	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); /* (2) */\
										\
		if (condition)		/* (3) */					\
			break;							\
										\
		if (___wait_is_interruptible(state) && __int) {			/* (4) */ \
			__ret = __int;						\
			goto __out;						\
		}								\
										\
		cmd; /* (5) */								\
	}									\
	finish_wait(&wq_head, &__wq_entry);				(6)	\
__out:	__ret;									\
})

以wait_event为例,当使用wait_event(wq_head, condition),最终会调用___wait_event(wq_head, condition, TASK_UNINTERRUPTIBLE, 0, 0, schedule())

(1) 定义一个wait_queue_entry, 并使用init_wait_entry进行初始化;
(2) 调用prepare_to_wait_event()将wait_queue_entry挂入链表,同时将当前任务状态设置为state(一般为TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE),准备修眠;
(3) 如果等待的条件已被满足,结束等待,跳出循环至(6);
(4) 如果在加入队列过程中进程(调用prepare_to_wait_event)睡眠状态被打断此处会结束等待过程;
(5) 对于wait_event接口,这个cmd是schedule(),此时任务被切走随眠
(6) 结束等待

从上述过程不难看出,等待事件condition的任务在等待条件被满足的过程中可能会被唤醒不只一次,condition不满足时,其会继续睡眠。如果这里的state被配置为TASK_INTERRUPTIBLE时,那么该进程被打断时,其会结束这个等待过程。

3 wake_up()

wake_up函数最终会调用__wake_up_common_lock(),这个函数的功能就是将等待链表上的任务一一唤醒,并将这些任务的wait_queue_entry从链表上摘下。这个函数里有一个比较有趣的机制,bookmark,正如其名书签,当wake的数目超过一个阈值WAITQUEUE_WALK_BREAK_CNT(默认是64)时,其会主动中断唤醒操作,尝试让出cpu,之后再从上次中断的地方继续唤醒。个人理解这是为了避免一次性唤醒的任务过多,一直占用CPU。

4 结语

上文分析了,最基本的wait_event()和wake_up()流程,其余诸如wait_event_timeout()等,大多类似,只在睡眠或唤醒时有一些细节上的差异,本次不再赘述,如果再发现有趣的点再补充吧。

Copyright © 2010-2022 ngui.cc 版权所有 |关于我们| 联系方式| 豫B2-20100000