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驱动中的等待队列与休眠的更多相关文章

  1. 【Linux驱动】内核等待队列

    在Linux中, 一个等待队列由一个"等待队列头"来管理,等待队列是双向链表结构. 应用场合:将等待同一资源的进程挂在同一个等待队列中. 数据结构 在include/linux/w ...

  2. Linux驱动:内核等待队列

    在Linux中, 一个等待队列由一个"等待队列头"来管理,等待队列是双向链表结构. 应用场合:将等待同一资源的进程挂在同一个等待队列中. 数据结构 在include/linux/w ...

  3. Linux驱动中的EPROBE_DEFER是个啥

    ​Linux kernel 驱动中,有不少驱动会引用到 EPROBE_DEFER 这个错误号.比如下面这个例子,对 devm_gpiod_get 的返回值进行判断,如果有错误且错误号不是 -EPRBO ...

  4. linux驱动中printk的使用注意事项

    今天在按键驱动中增加printk(KERN_INFO "gpio_keys_gpio_isr()\n");在驱动加载阶段可以输出调试信息,但驱动加载起来后的信息,在串口端看不到输出 ...

  5. Linux驱动中completion接口浅析(wait_for_complete例子,很好)【转】

    转自:http://blog.csdn.net/batoom/article/details/6298267 completion是一种轻量级的机制,它允许一个线程告诉另一个线程工作已经完成.可以利用 ...

  6. Linux驱动中completion接口浅析(wait_for_complete例子,很好)

    completion是一种轻量级的机制,它允许一个线程告诉另一个线程工作已经完成.可以利用下面的宏静态创建completion:                          DECLARE_CO ...

  7. 为什么linux驱动中变量或者函数都用static修饰?(知乎问题)

    static定义的全局变量 或函数也只能作用于当前的文件. 世界硬件厂商太多,定义static为了防止变量或 函数 重名,定义成static, 就算不同硬件驱动中的 变更 或函数重名了也没关系 .

  8. Linux驱动中常用的宏

    .module_i2c_driver(adxl34x_driver)展开为 static int __int adxl34x_driver_init(void) { return i2c_regist ...

  9. Linux驱动中的platform总线分析

    copy from :https://blog.csdn.net/fml1997/article/details/77622860 概述 从Linux2.6内核起,引入一套新的驱动管理和注册机制:pl ...

  10. Linux驱动中获取系统时间

    最近在做VoIP方面的驱动,总共有16个FXS口和FXO口依次初始化,耗用的时间较多.准备将其改为多线程,首先需要确定哪个环节消耗的时间多,这就需要获取系统时间. #include <linux ...

随机推荐

  1. 9.3K+ Star!一个面向中小型企业设计的开源运维平台!

    大家好,我是Java陈序员. 我们在日常开发中,会有很多的应用环境,开发环境.测试环境.回归环境.生产环境等等. 这些环境,需要部署在一台台的服务器上,有的可能是物理机,有的可能是云服务器. 那么,这 ...

  2. ABAP 7.58 中支持任意精度算术的新类

    1. 引言 通常,有两种对编程语言的改进.第一种是让困难的事情变得简单,第二种是让不可能的事情变为可能.本文介绍的是任意精度算术,它属于第二类:使在ABAP中原本不可能的事情成为可能. 过去已经可以在 ...

  3. 对C语言符号的一些冷门知识运用的剖析和总结

    符号 目录 符号 注释 奇怪的注释 C风格的注释无法嵌套 一些特殊的注释 注释的规则建议 反斜杠'\' 反斜杠有续行的作用,但要注意续行后不能添加空格 回车也能起到换行的作用,那续行符的意义在哪? 反 ...

  4. CF933-Div3 大致思路+题解

    \(Rank\) A - Rudolf and the Ticket 纯水题 暴力枚举直接过 $code$ #include<bits/stdc++.h> #define fo(x,y,z ...

  5. 设置MySQL数据库的远程连接权限

    解决方案 在服务器上登录数据库,然后执行如下授权SQL语句.该授权SQL语句的含义为root用户可用任何IP地址登录数据库,操作任何数据库中的任何对象.   GRANT ALL PRIVILEGES ...

  6. [chatGPT]unity中,我希望一个角色有一个链表能获取到场上所有“creature”的transform,当creature增加或减少时刷新这个链表,我该怎么做?

    关键字:unity游戏对象管理,unity,unity实例管理,unity触发方法 我 unity中,我希望一个角色有一个链表能获取到场上所有"creature"的transfor ...

  7. minio 安装

    mybatis 相关:https://baomidou.com/pages/223848/#fieldfillhttps://mybatis-flex.com/zh/intro/maven.html ...

  8. 高分辨率食道测压(HRM)

    高分辨率测压(High resolution Manometry) HRM的优势 高分辨率食管测压不但实现了从咽部到胃部的全程功能监测,而且插管无需牵拉,操作十分方便.更为重要的是,临床医生经过简单的 ...

  9. Opencv笔记(13)积分图

    积分图时一种允许子区域快速求和的数据结构,这种求和在很多方面都很有用,值得一提的是haar小波的计算,它用于人脸识别和类似的算法.Opencv支持积分图的三种变体,分别是总和.平方求和以及倾斜求和.每 ...

  10. FlashDuty Changelog 2023-09-07 | 新增深色模式与主题配置

    FlashDuty:一站式告警响应平台,前往此地址免费体验! FlashDuty 现在已经全面支持了深色模式,这为您提供了更柔和的光线和舒适的界面外观.并且,您可以根据自己的喜好和使用环境动态切换深色 ...