Linux驱动中的等待队列与休眠
Linux驱动中的等待队列与休眠
原文:https://blog.csdn.net/mengluoxixiang/article/details/46239523?spm=1001.2014.3001.5501
背景
在Linux内核驱动中常常会存在这种情况:进程A若想继续执行需要满足某个条件condition的限制,若条件不满足则进程会被挂到等待队列进行等待。
在Linux中,一个等待队列由一个“等待队列头”来管理,看一下这个队列头的初始化:
DECLARE_WAIT_QUEUE_HEAD(name)
或动态的定义初始化:
wait_queue_head_t my_queue_head;
init_waitqueue_head(&my_queue_head);
wait 函数
下面分成三种情况看一下有关wait函数的操作:
普通的wait函数
接着看一下满足不同操作条件的wait函数。
(1)不可中断,没有超时的wait函数:
#define wait_event(wq, condition)
do {
//判断,如果条件为真则不用调用等待队列,继续执行
if (condition)
break;
__wait_event(wq, condition); //调用等待队列
} while (0)
#define __wait_event(wq, condition>
do {
DEFINE_WAIT(__wait);//定义一个等待线程
for (;;) {
//将当前进程挂到等待队列,并设置当前状态
prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);
if (condition)
break;
schedule();//进程调度,使等待队列开始休眠
}
//把唤醒的进程从等待队列中去掉,并设置当前状态为TASK_RUNNING
finish_wait(&wq, &__wait);
} while (0)
该方法在不满足条件condition时会进行等待,一直等条件满足为止,期间没有时间限制,不允许中断
(2)不可中断,有超时限制的wait函数
#define wait_event_timeout(wq, condition, timeout)
({
long __ret = timeout; //设置最大等待时间
if (!(condition)) //如果条件不满足,调用等待队列
__wait_event_timeout(wq, condition, __ret);
__ret;
})
#define __wait_event_timeout(wq, condition, ret)
do {
DEFINE_WAIT(__wait);//定义一个等待线程
for (;;) {
//将当前进程挂到等待队列,并设置当前状态
prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);
if (condition)
break;
//进程调度,使等待队列开始休眠,且有最大休眠时间限制
ret = schedule_timeout(ret);
if (!ret)
break;
}
//把唤醒的进程从等待队列中去掉,并设置当前状态为TASK_RUNNING
finish_wait(&wq, &__wait);
} while (0)
该方法在超时情况下未能得到满足的条件会返回0,否则返回剩余的可等待时间,期间不可中断。
(3)可中断,没有超时限制的wait函数
#define wait_event_interruptible(wq, condition)
({
int __ret = 0;
if (!(condition))//如果条件不满足,调用等待队列
__wait_event_interruptible(wq, condition, __ret);
__ret;
})
#define __wait_event_interruptible(wq, condition, ret)
do {
DEFINE_WAIT(__wait);//定义一个等待线程
for (;;) {
prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);
if (condition)
break;
if (!signal_pending(current)) {
schedule();
continue;
}
ret = -ERESTARTSYS;
break;
}
finish_wait(&wq, &__wait);//把唤醒的进程从等待队列中去掉,并设置当前状态为TASK_RUNNING
} while (0)
该方法在等待时如果被中断会返回-ERESTARTSYS信号,否则在满足condition条件时返回0,期间没有时间限制。
(4)可中断,有超时限制的wait函数
#define wait_event_interruptible_timeout(wq, condition, timeout)
({
long __ret = timeout;
if (!(condition))
__wait_event_interruptible_timeout(wq, condition, __ret);
__ret;
})
#define __wait_event_interruptible_timeout(wq, condition, ret)
do {
DEFINE_WAIT(__wait);
for (;;) {
prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);
if (condition)
break;
if (!signal_pending(current)) {
ret = schedule_timeout(ret);
if (!ret)
break;
continue;
}
ret = -ERESTARTSYS;
break;
}
finish_wait(&wq, &__wait);
} while (0)
该方法若超时结束会返回0,中断结束返回-ERESTARTSYS信号,否则在满足条件时返回剩余可等待时间。
iowait
什么是iowait? 顾名思义,就是系统因为io导致的进程wait。
再深一点讲就是:这时候系统在做io,导致没有进程在干活,cpu在执行idle进程空转,
所以说iowait的产生要满足两个条件,一是进程在等io,二是等io时没有进程可运行。
(1)可中断无超时iowait函数
#define __wait_io_event_interruptible(wq, condition, ret)
do {
DEFINE_WAIT(__wait);
for (;;) {
prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);
if (condition)
break;
if (!signal_pending(current)) {
io_schedule();
continue;
}
ret = -ERESTARTSYS;
break;
}
finish_wait(&wq, &__wait);
} while (0)
(2)有中断有超时iowait函数
#define __wait_io_event_interruptible_timeout(wq, condition, ret)
do {
DEFINE_WAIT(__wait);
for (;;) {
prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);
if (condition)
break;
if (!signal_pending(current)) {
ret = io_schedule_timeout(ret);
if (!ret)
break;
continue;
}
ret = -ERESTARTSYS;
break;
}
finish_wait(&wq, &__wait);
} while (0)
互斥wait函数
接下来看一下互斥等待函数:
正常情况下,当一个进程调用一个等待队列的wake_up函数时,所有这个队列上的进程都被置为可运行的,然而在许多情况下,我们提前知道只有一个进程将被唤醒并能成功的使用资源,其余被被唤醒的进程将再次进入等待,由此看来这种行为会降低系统的性能。因为内核开发者开发了一种“互斥等待”添加到内核中,与普通wait函数不同的是:
1、当一个进程有WQ_FLAG_EXCLUSEVE标志时,它被添加到等待队列的队尾,否则添加到对头。
2、当一个队列上的wake_up函数被调到用时,在唤醒第一个带有WQ_FLAG_EXCLUSEVE标志的进程后停止,但内核仍会调用所有非互斥等待的进程,因为这些进程排在队首,互斥进程排在队尾,而唤醒的顺序是由首到尾。
最后的结果是以顺序的方式唤醒第一个互斥的进程后停止唤醒后面的进程。
使用互斥等待需要满足三个条件:
1、希望该进程对资源进行有效竞争,
2、当资源可用时唤醒一个进程就足够完全消耗资源,
3、所有使用该资源的进程都应该统一的使用互斥等待。
(1)可中断,无超时限制的互斥wait函数
#define __wait_event_interruptible_exclusive(wq, condition, ret)
do {
DEFINE_WAIT(__wait);
for (;;) {
prepare_to_wait_exclusive(&wq, &__wait,
TASK_INTERRUPTIBLE);
if (condition) {
finish_wait(&wq, &__wait);
break;
}
if (!signal_pending(current)) {
schedule();
continue;
}
ret = -ERESTARTSYS;
abort_exclusive_wait(&wq, &__wait, //把该进程的状态回复成running,并从等待队列中删除
TASK_INTERRUPTIBLE, NULL);
break;
}
大部分会用到的进入wait的函数已经分析完了,下面看一下相关的wake_up函数:
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)//唤醒非中断的一个进程
#define wake_up_nr(x, nr) __wake_up(x, TASK_NORMAL, nr, NULL)//唤醒非中断的nr个进程
#define wake_up_all(x) __wake_up(x, TASK_NORMAL, 0, NULL)//唤醒非中断的所有进程
#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)//唤醒可中断的nr个进程
#define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)//唤醒可中断的所有进程
重点看一下这几个define里都调用的函数__wake_up()
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);
}
EXPORT_SYMBOL(__wake_up);
上面那个函数先加锁再调用__wake_up_common(),然后解锁,继续看__wake_up_common()
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) {
unsigned flags = curr->flags;
if (curr->func(curr, mode, wake_flags, key) &&
(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
break;
}
}
上面这个函数很巧妙,如果if()的条件不满足的话则遍历所有列表中的结点,否则结束遍历。
看一下会使遍历结束的if中的三个条件:
1、curr->fun(),调用该结点的fun()函数唤醒当前进程,如果唤醒失败则后面的两个条件都不用判断,继续遍历下一个节点
2、flags & WQ_FLAG_EXCLUSIVE ,如果该条件不成立(即不是互斥等待)后面的条件就不用判断了,继续遍历
3、--nr_exclusive,在前两个条件都成立的情况下,nr_exclusive变量先减1,再判断是否为0 ,
也就是说只有在flags & WQ_FLAG_EXCLUSIVE成立时才会对nr_exclusive的值进行自减和判断,
到nr_exclusive自减变为0时,三个条件均成立,此时跳出循环,实现的操作是:
唤醒所有非互斥进程,接着唤醒nr_exclusive个互斥进程后停止唤醒。
这里需要注意的时:当nr_exclusive0时,(!--nr_exclusive)1恒成立,也就是说唤醒所有互斥进程。
Linux驱动中的等待队列与休眠的更多相关文章
- 【Linux驱动】内核等待队列
在Linux中, 一个等待队列由一个"等待队列头"来管理,等待队列是双向链表结构. 应用场合:将等待同一资源的进程挂在同一个等待队列中. 数据结构 在include/linux/w ...
- Linux驱动:内核等待队列
在Linux中, 一个等待队列由一个"等待队列头"来管理,等待队列是双向链表结构. 应用场合:将等待同一资源的进程挂在同一个等待队列中. 数据结构 在include/linux/w ...
- Linux驱动中的EPROBE_DEFER是个啥
Linux kernel 驱动中,有不少驱动会引用到 EPROBE_DEFER 这个错误号.比如下面这个例子,对 devm_gpiod_get 的返回值进行判断,如果有错误且错误号不是 -EPRBO ...
- linux驱动中printk的使用注意事项
今天在按键驱动中增加printk(KERN_INFO "gpio_keys_gpio_isr()\n");在驱动加载阶段可以输出调试信息,但驱动加载起来后的信息,在串口端看不到输出 ...
- Linux驱动中completion接口浅析(wait_for_complete例子,很好)【转】
转自:http://blog.csdn.net/batoom/article/details/6298267 completion是一种轻量级的机制,它允许一个线程告诉另一个线程工作已经完成.可以利用 ...
- Linux驱动中completion接口浅析(wait_for_complete例子,很好)
completion是一种轻量级的机制,它允许一个线程告诉另一个线程工作已经完成.可以利用下面的宏静态创建completion: DECLARE_CO ...
- 为什么linux驱动中变量或者函数都用static修饰?(知乎问题)
static定义的全局变量 或函数也只能作用于当前的文件. 世界硬件厂商太多,定义static为了防止变量或 函数 重名,定义成static, 就算不同硬件驱动中的 变更 或函数重名了也没关系 .
- Linux驱动中常用的宏
.module_i2c_driver(adxl34x_driver)展开为 static int __int adxl34x_driver_init(void) { return i2c_regist ...
- Linux驱动中的platform总线分析
copy from :https://blog.csdn.net/fml1997/article/details/77622860 概述 从Linux2.6内核起,引入一套新的驱动管理和注册机制:pl ...
- Linux驱动中获取系统时间
最近在做VoIP方面的驱动,总共有16个FXS口和FXO口依次初始化,耗用的时间较多.准备将其改为多线程,首先需要确定哪个环节消耗的时间多,这就需要获取系统时间. #include <linux ...
随机推荐
- 如何查看Navicat已有连接的密码(简单清晰)
1.打开Navicat,File > Export Connections 2.选择你想查看的数据库,并勾选下方的 [导出密码],导出 3.去文件里找到Password 4.打开网址 https ...
- vim 使用black 格式化python代码
vim 使用black 格式化代码 github black 的github https://github.com/psf/black 安装 pip3 install black 使用 black f ...
- 【内存优化】Oracle 的SGA与Linux的shmall和shmmax的关联
查看linux下的Oracle共享内存段 [oracle@oradb ~]$ ipcs -m ------ Shared Memory Segments -------- key shmid owne ...
- git checkout 命令图文详解
目录 git checkout branchname (切换本地分支) 切换远程分支 放弃修改 git checkout . git checkout – filename git checkout ...
- 海康威视web插件安装后,谷歌浏览器还是不能看视频问题
首先要根据弹出的信息提示,下载并安装视频播放插件, 安装完成后重新打开谷歌浏览器,重新登录系统,如果还是不能看视频,请按下面的方法设置: 步骤1:谷歌浏览器,地址栏中输入:chrome://flags ...
- pageoffice6 实现提取数据区域为子文件(Word拆分)
在实际的开发过程中,有时会遇到希望提取Word文档中部分内容保存为子文件的需求,PageOffice支持提取Word文档数据区域中的内容为一个Word文件流,在服务器端创建PageOffice的Wor ...
- pageoffice6在线编辑word 文件禁止鼠标右键
有时让用户使用PageOffice只读模式(OpenModeType.docReadOnly)打开Word文件后,为了更好的只读效果,还希望禁用Word中的右键菜单,实现此效果只需创建com.zhuo ...
- go高并发之路——启航
工作7年有余了,B端和C端业务都做过不少,打算整理分享一些自己在实际工作中所遇到的高并发的场景和解决方案,也是对自己本人职业生涯中的一些经验的总结和感悟.与其他博文略有不同的是,这些基本上都是自己实际 ...
- 终于搞懂了!原来 Vue 3 的 generate 是这样生成 render 函数的
前言 在之前的 面试官:来说说vue3是怎么处理内置的v-for.v-model等指令? 文章中讲了transform阶段处理完v-for.v-model等指令后,会生成一棵javascript AS ...
- MySQL知识网络
MySQL知识网络 引擎 InnoDB 支持表锁 .行锁 支持事务 *.frm 表结构文件 *.idb 表数据和索引文件 MyISAM 支持表锁 *.frm 表结构文件 *.MYD 表数据文件 *.M ...