wait_queue机制浅析

el/2023/12/3 3:06:24

wait_queue机制浅析

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

taskAschedule_systemtaskBdo somethingwait_eventschedulesleepthe event happenedwake_upschedulescheduledo somthingtaskAschedule_systemtaskB
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()等,大多类似,只在睡眠或唤醒时有一些细节上的差异,本次不再赘述,如果再发现有趣的点再补充吧。


http://www.ngui.cc/el/1113881.html

相关文章

man命令使用指南

man命令是linux下查找shell命令、函数等使用方法的利器。最简单的使用方式是man <the thing you want>。掌握上面那条命令应该也可以满足80%的使用场景了。这里记录一些更加深入的man命令使用的方法&#xff0c;如果还不能满足查询需求&#xff0c;就只能man man再深挖了…

Vscode 搭建舒适的 Markdown 编辑环境

文章目录1. 显示风格2. 图片插入3. 表格处理4 其他1. 显示风格 使用 Markdown notebook(Microsoft)&#xff0c;这个插件可以实现markdown的预览和编辑在同一页面下&#xff0c;显示效果如下。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 KeyComma…

关于++,--的理解

package cn.nrsc.demo01; /** , -- : 增量语句, 用来对变量的自身进行操作的* 解释:* : 对变量的自身进行1操作* --: 对变量的自身进行-1操作* * 使用分为两种:* 单独使用:* ,--写在变量的前面或者是后面,最终的结果是一样的. * 单独使用: 就是变量自身单独成立一行, 没有…

JAVA 强制数据类型转换和隐式数据类型转换

package cn.nrsc.demo01; /** 变量的数据类型转换: (了解)* 强制数据类型转换:* 小的数据类型 变量名 (小的数据类型)大的数据类型的值或者变量* byte < short, char < int < long < float < double* * 占用字节: 1 2 2 …

逻辑运算符与()、或(|)、非(!)、异或(^)及双与()和双或(||)

1 、与(&), 或(|),非(!),异或(^) package cn.nrsc.demo02; /** 逻辑运算符: &, |, ^, !* &(与): 只要有一边为fale, 那么就是false* |(或): 只要有一边为true, 那么就是true* ^(异或): 只要是相同的boolean值, 那么就是false, 不相同才是true* 解释: 用来连接bo…

JAVA 基本数据类型(4大类8小种)

文章目录1、变量的定义及基本数据类型的介绍2、变量定义容易犯的错误1、变量的定义及基本数据类型的介绍 /* 变量的定义格式: 第一种初始化方式: 数据类型 变量名 赋值;第二种初始化方式: 数据类型 变量名; 变量名 赋值;如果变量不赋值,不能直接使用!java中一共有2大数据类型…

JAVA 标识符

/* 标识符: 给类,变量, 方法, 接口….来命令的一种规范. 组成: 字母(a-z, A-Z), 数字(0-9), 下划线(_), 美元符号($), 人民币符号(&#xffe5;). 规则: 1. 数字不能开头 2. 长度没有限制 3. 不能是java中的关键字 4. 最好做到见名知意 常见的命名规范: 给变量,方法起…

Java 引用数据类型的使用过程

/* * java中数据类型分为两种(大类) * 基本数据类型 * * * 引用数据类型(先作为了解) * 但凡是引用数据类型使用过程, 一般可以分成三个步骤: * 1. 导入包: 导入要使用的类所在的包 * 使用import 进行导入 * 如果是该类在java.lang包下的类,可以直接使用, 不要导包 *…

巧用while(true){ }死循环的一个小例子

package cn.nrsc.zuoye_while_true;/** 请按如下要求编写程序,打印菜单:1.从键盘上录入一个1到5的数字;2.当数字为1时打印菜单"新建";当数字为2时打印菜单"打开文件";当数字为3时打印菜单"保存";当数字为4时打印菜单"刷新";当数字为5时…

While_continue 语句里容易出现的一个死循环及解决办法

package cn.nrsc.while_continue; /** continue: 跳出本次循环, 继续进行下一次循环* * continue的使用场景: 只能在循环* */ /** 需求:使用while循环, 输出10次HelloWorld.请在跳过第4次输出.* */public class While_Continue {public static void main(String[] args) {int i…