linux wait函数头文件_手把手教Linux驱动9-等待队列waitq

在上一篇《手把手教Linux驱动8-Linux IO模型》我们已经了解了阻塞、非阻塞、同步和异步等相关概念,本文主要讲解如何通过等待队列实现对进程的阻塞。

应用场景:

当进程要获取某些资源(例如从网卡读取数据)的时候,但资源并没有准备好(例如网卡还没接收到数据),这时候内核必须切换到其他进程运行,直到资源准备好再唤醒进程。

waitqueue (等待队列) 就是内核用于管理等待资源的进程,当某个进程获取的资源没有准备好的时候,可以通过调用 add_wait_queue() 函数把进程添加到 waitqueue 中,然后切换到其他进程继续执行。当资源准备好,由资源提供方通过调用 wake_up() 函数来唤醒等待的进程。

定义头文件:

#include 

定义和初始化等待队列头(workqueue):

静态的,用宏:

#define DECLARE_WAIT_QUEUE_HEAD(name)     wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

动态的,也是用宏:

#define init_waitqueue_head(q)                  do {                                static struct lock_class_key __key;                                     __init_waitqueue_head((q), #q, &__key);     } while (0)

定义实例

wait_queue_head_t wq;init_waitqueue_head(&wq);

阻塞接口:

wait_event(wq, condition)wait_event_timeout(wq, condition, timeout)wait_event_interruptible(wq, condition)wait_event_interruptible_timeout(wq, condition, timeout)wait_event_hrtimeout(wq, condition, timeout)wait_event_interruptible_hrtimeout(wq, condition, timeout)wait_event_interruptible_exclusive(wq, condition)wait_event_interruptible_locked(wq, condition)wait_event_interruptible_locked_irq(wq, condition)wait_event_interruptible_exclusive_locked(wq, condition)wait_event_interruptible_exclusive_locked_irq(wq, condition)wait_event_killable(wq, condition)wait_event_lock_irq_cmd(wq, condition, lock, cmd)wait_event_lock_irq(wq, condition, lock)wait_event_interruptible_lock_irq_cmd(wq, condition, lock, cmd)wait_event_interruptible_lock_irq(wq, condition, lock)wait_event_interruptible_lock_irq_timeout(wq, condition, lock,  timeout)

参数

wq        定义的等待队列头,condition 为条件表达式,当wake up后,condition为真时,唤醒阻塞的进程,为假时,继续睡眠。

功能说明

接口版本比较多,各自都有自己合适的应用场合,但是常用的是前面四个。

wait_event:不可中断的睡眠,条件一直不满足,会一直睡眠。wait_event_timeout:不可中断睡眠,当超过指定的timeout(单位是jiffies)时间,不管有没有wake up,还是条件没满足,都要唤醒进程,此时返回的是0。在timeout时间内条件满足返回值为timeout或者1;wait_event_interruptible:可被信号中断的睡眠,被信号打断唤醒时,返回负值-ERESTARTSYS;wake up时,条件满足的,返回0。除了wait_event没有返回值,其它的都有返回,有返回值的一般都要判断返回值。如下例:    int flag = 0;    if(wait_event_interruptible(&wq,flag == 1))        return -ERESTARTSYS;wait_event_interruptible_timeout:是wait_event_timeout和wait_event_interruptible_timeout的结合版本,有它们两个的特点。

其他的接口,用的不多,有兴趣可以自己看看。

解除阻塞接口(唤醒)

接口定义:

#define wake_up(x)          __wake_up(x, TASK_NORMAL, 1, NULL)#define wake_up_nr(x, nr)       __wake_up(x, TASK_NORMAL, nr, NULL)#define wake_up_all(x)          __wake_up(x, TASK_NORMAL, 0, NULL)#define wake_up_locked(x)       __wake_up_locked((x), TASK_NORMAL, 1)#define wake_up_all_locked(x)       __wake_up_locked((x), TASK_NORMAL, 0)#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)#define wake_up_interruptible_all(x)    __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)#define wake_up_interruptible_sync(x)   __wake_up_sync((x), TASK_INTERRUPTIBLE, 1)

功能说明

wake_up:一次只能唤醒挂在这个等待队列头上的一个进程wake_up_nr:一次唤起nr个进程(等待在同一个wait_queue_head_t有很多个)wake_up_all:一次唤起所有等待在同一个wait_queue_head_t上所有进程wake_up_interruptible:对应wait_event_interruptible版本的wake upwake_up_interruptible_sync:保证wake up的动作原子性,wake_up这个函数,很有可能函数还没执行完,就被唤起来进程给抢占了,这个函数能够保证wak up动作完整的执行完成。

其他的也是与对应阻塞接口对应的。

使用实例

以字符设备为例,在没有数据的时候,在read函数中实现读阻塞,当向内核写入数据时,则唤醒阻塞在该等待队列的所有任务。

读操作

static ssize_t hello_read(struct file *filp,char __user *buf,size_t size,loff_t *poss)  {      wait_event_interruptible(rwq,flage!=0);    ……………    flage=0;    wake_up_interruptible(&wwq);    return size;  }   

写操作

static ssize_t hello_write(struct file *filp,const char __user *buf,size_t size,loff_t *poss)  {      wait_event_interruptible(wwq,flage!=1);    ……………    flage=1;    wake_up_interruptible(&rwq);    return size;  }    

如何同步支持非阻塞?

上述操作虽然实现了阻塞功能,但是我们在应用程序打开一个字符设备的时候,有时候我们希望操作是非阻塞的,比如:

fd=open("/dev/hello",O_RDONLY|O_NONBLOCK); 

那么驱动如何得到这个标记呢?

参考《手把手教Linux驱动6-inode,file,file_operations关系》,该标记会存储在结构体struct file的f_flags成员中。

所以程序可以修改如下:

static ssize_t hello_read(struct file *filp,char __user *buf,size_t size,loff_t *poss)  {  int ret = 0;if(flage==0){if(filp->f_flags & O_NONBLOCK){  return -EAGAIN;}wait_event_interruptible(rwq,flage!=0);}        ……………flage=0;wake_up_interruptible(&wwq);    return size;  }

一种灵活的添加删除等待队列头中的等待队列:

(1)定义:
静态:

#define DECLARE_WAITQUEUE(name, tsk)                        wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)

(2)动态:

wait_queue_t wa;init_waitqueue_entry(&wa,&tsk);

tsk是进程结构体,一般是current(linux当前进程就是用这个获取)。还可以用下面的,设置自定义的等待队列回调函数,上面的是linux默认的一个回调函数default_wake_function(),不过默认的用得最多:

wait_queue_t wa;wa->private = &tsk;int func(wait_queue_t *wait, unsigned mode, int flags, void *key){    //}init_waitqueue_func_entry(&wa,func);

(回调有什么作用?)
用下面函数将等待队列,加入到等待队列头(带remove的是从工作队列头中删除工作队列):

extern void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);extern void add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait);extern void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

上面的阻塞和解除阻塞接口,只能是对当前进程阻塞/解除阻塞,有了这几个灵活的接口,我们可以单独定义一个等待队列,只要获取进程task_struct指针,我们可以将任何进程加入到这个等待队列,然后加入到等待队列头,我们能将其它任何进程(不仅仅是当前进程),挂起睡眠,当然唤醒时,如果用wake_up_all版本的话,也会一同唤起。这种情况,阻塞不能用上面的接口了,我们需要用下一节讲述的接口(schedule()),解除阻塞可以用wake_up,wake_up_interruptible等。

更高级灵活的阻塞:

阻塞当前进程的原理:用函数set_current_state()修改当前进程为TASK_INTERRUPTIBLE(不可中断睡眠)或TASK_UNINTERRUPTIBLE(可中断睡眠)状态,然后调用schedule()告诉内核重新调度,由于当前进程状态已经为睡眠状态,自然就不会被调度。schedule()简单说就是告诉内核当前进程主动放弃CPU控制权。这样来,就可以说当前进程在此处睡眠,即阻塞在这里。

在上一小节“灵活的添加删等待队列头中的等待队列”,将任意进程加入到waitqueue,然后类似用:

task_struct *tsk;wait_queue_t wa;//假设tsk已经指向某进程控制块p->state = TASK_INTERRUPTIBLE;//or TASK_UNINTERRUPTIBLEinit_waitqueue_entry(&wa,&tsk);

就能将任意进程挂起,当然,还需要将wa,挂到等待队列头,然后用wait_event(&wa),进程就会从就绪队列中退出,进入到睡眠队列,直到wake up时,被挂起的进程状态被修改为TASK_RUNNING,才会被再次调度。(主要是schedule()下面会说到)。


wait_event实现原理:

先看下wait_event实现:

#define __wait_event(wq, condition)                     do {                                        DEFINE_WAIT(__wait);                                                                for (;;) {                                  prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);            if (condition)                                  break;                              schedule();                         }                                   finish_wait(&wq, &__wait);                  } while (0)#define wait_event(wq, condition)                   do {                                        if (condition)                                  break;                              __wait_event(wq, condition);                    } while (0)

DEFINE_WAIT:

定义一个工作队列。

prepare_to_wait:

定义:void prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)功能:将工作队列wait加入到工作队列头q,并将当前进程设置为state指定的状态,一般是TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE状态(在这函数里有调用set_current_state)。    第一个参数:工作队列头    第二个参数:工作队列    第三个参数:当前进程要设置的状态

finish_wait

用了prepare_to_wait之后,当退出时,一定要用这个函数清空等待队列。

功能:

  1. 该函数首先调用prepare_to_wait,修改进程到睡眠状态,
  2. 条件不满足,schedule()就放弃CPU控制权,睡眠,
  3. 当wake up的时候,阻塞在wq(也可以说阻塞在wait_event处)等待队列头上的进程,再次得到运行,接着执行schedule()后面的代码,这里,显然是个循环,prepare_to_wait再次设置当前进程为睡眠状态,然后判断条件是否满足,
  4. 满足就退出循环,finish_wait将当前进程恢复到TASK_RUNNING状态,也就意味着阻塞解除。不满足,继续睡下去。如此反复等待条件成立。

明白这个过程,用prepare_to_wait和schedule()来实现更为灵活的阻塞,就很简单了,解除阻塞和前面的一样用wake_up,wake_up_interruptible等。

下面是wake_up和wait_event流程图:

ef7194c7916d4046e1f2d718a996ade5.png

wait_event和wake_up流程

独占等待


当调用wake_up时,所有等待在该队列上的进程都被唤醒,并进入可运行状态如果只有一个进程可获得资源,此时,其他的进程又将再次进入休眠,如果数量很大,被称为”疯狂售群”。这样会非常占用系统资源。

解决方法:

wait_queue_t成员flage有个重要的标志WQ_FLAG_EXCLUSIVE,表示:

当一个等待队列入口有 WQ_FLAG_EXCLUSEVE 标志置位, 它被添加到等待队列的尾部. 没有这个标志的入口项, 添加到开始.当 wake_up 被在一个等待队列上调用, 它在唤醒第一个有WQ_FLAG_EXCLUSIVE 标志的进程后停止.

wait_event默认总是将waitqueue加入开始,而wake_up时总是一个一个的从开始处唤醒,如果不断有waitqueue加入,那么最开始加入的,就一直得不到唤醒,有这个标志,就避免了这种情况。

prepare_to_wait_exclusive()就是加入了这个标志的。

补充

Linux将进程状态描述为如下五种:1. TASK_RUNNING:可运行状态。处于该状态的进程可以被调度执行而成为当前进程。2. TASK_INTERRUPTIBLE:可中断的睡眠状态。处于该状态的进程在所需资源有效时被唤醒,也可以通过信号或定时中断唤醒(因为有signal_pending()函数)。3. TASK_UNINTERRUPTIBLE:不可中断的睡眠状态。处于该状态的进程仅当所需资源有效时被唤醒。4. TASK_ZOMBIE:僵尸状态。表示进程结束且已释放资源,但其task_struct仍未释放。5. TASK_STOPPED:暂停状态。处于该状态的进程通过其他进程的信号才能被唤醒
8c3b2d52f4e31a4d9239ffe3efc38d88.png

Linux 通过结构体task_struct维护所有运行的线程、进程,不同状态的任务,会由不同的队列进行维护,schedule()函数就负责根据这些状态的变化调度这些任务。关于进程的调度,后续会新开文章详细介绍。

实例


下面实例主要功能是基于我们之前课程《手把手教Linux驱动3-之字符设备架构详解,有这篇就够了》最后的代码实例,在此基础上增加写阻塞的功能。

  1. 内核中有缓冲内存,以及是否可以访问的标记;
int flage=0;  //1:有数据可读  0:无数据,不可读char kbuf[128];
  1. 初始状态下flage为0,没有数据;
  2. 应用进程读取数据会调用到内核函数hello_read(),如果flage为1,则直接读走数据,并将改flage置1,如果flage为0,则进程阻塞,直到有进程写入数据将该flage置1;
  3. 应用进程每次写入数据会调用到内核函数hello_write(),如果flage为0,则直接写入数据,并设置flage为1,如果为1,则阻塞,直到有其他进程调用到读函数hello_read()将flage置0。

驱动

ule_exit(hellodev_exit);  

测试程序

read.c

#include #include #include #include #include #include int main(){  int fd= 0;  char buf[128];  int num;//  fd=open("/dev/hello",O_RDONLY); 阻塞方式读取   fd=open("/dev/hello",O_RDONLY|O_NONBLOCK);  //非阻塞     if(fd<0)   {         printf("open memdev failed!");         return -1;                 }       read(fd,buf,sizeof(buf));      printf("num:%s",buf);    close(fd);  return 0;       }

write.c

#include #include #include #include #include #include int main(){  int fd =0;  char buf[128]="hello yikouLlinux";  int num;​  fd=open("/dev/hello",O_RDWR);       if(fd <0)       {         printf("open device failed!");         return -1;                 }       write(fd,buf,sizeof(buf));    close(fd);  return 0;       }

掌握了等待队列的用法,后面我们就可以进行中断的讲解了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/329750.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HoloLens开发手记-配置开发环境 Install the tools

随着Build 2016开发者大会的结束&#xff0c;HoloLens开发包也正式开放下载。Hololens没有独立的SDK&#xff0c;开发特性被集成到最新的Visual Studio Update 2中。如果你没有HoloLens真机&#xff0c;那么可以安装HoloLens模拟器。 安装清单 注意: 这里为了方便大家顺利下载安…

kafka再均衡监听器测试

【README】 本文使用的kafka是最新的 kafka3.0.0&#xff1b;本文kafka集群有3个节点分别是 centos201, centos202, centos203 &#xff1b; brokerid 分别为 1,2&#xff0c;3&#xff1b;本文主要用于测试 再均衡监听器&#xff1b;当有新消费者加入时&#xff0c;会发生分区…

java面试常考系列四

转载自 java面试常考系列四 题目一 大O符号(big-O notation)的作用是什么&#xff1f;有哪些使用方法&#xff1f; 大O符号描述了当数据结构里面的元素增加的时候&#xff0c;算法的规模或者是性能在最坏的场景下有多么好。大O符号也可用来描述其他的行为&#xff0c;比如&…

用python进行自然语言处理_Python自然语言处理示例:SVM和贝叶斯分类

❝关于自然语言处理(NLP)方面的文章、书籍非常之多&#xff0c;对于自然语言处理的上手&#xff0c;很多人是不知所措的。通过对本文的浏览&#xff0c;您应该能够对自然语言处理会有一个能够完整的有趣的基于Python的对自然语言处理的了解。❞什么是文本分类文本分类是将文本按…

Build 2016,你可能忽视的几个细节

微软公司主办的Build 2016大会尚在进程中&#xff0c;但是两场重量级的主题演讲已经结束。下面列举了我个人非常关注的几个细节&#xff0c;介绍一些背景知识以饲读者。 Bash on Windows背后的历史和未来 微软和IBM二十多年前联合开发NT内核的时候就已经为接驳多种操作系统留下…

JAVA面试常考系列五

转载自 JAVA面试常考系列五 题目一 串行(serial)收集器和吞吐量(throughput)收集器的区别是什么&#xff1f;吞吐量收集器使用并行版本的新生代垃圾收集器&#xff0c;它用于中等规模和大规模数据的应用程序。串行收集器对大多数的小应用(在现代处理器上需要大概100M左右的内存…

kafka消费者开发方式小结

【README】 1&#xff0c; 本文总结了 kafka消费者开发方式&#xff1b;2&#xff0c; 本文使用的是最新的kafka版本 3.0.0&#xff1b;【1】 kafka消费则 【1.1】消费者与消费者组 1&#xff09;消费者&#xff1a; 应用程序需要创建消费者对象&#xff0c;订阅主题并开始接…

微软发布Azure Functions、Service Fabric和IoT Starter Kits新服务

微软此次 Build 2016 大会的重点主题一直都围绕开发和 Microsoft Azure 云服务&#xff0c;今天更是对外发布了 Azure Functions、Service Fabric 和 IoT Starter Kit 等一系列新服务。就目前与其它友商的竞争而言&#xff0c;微软近期不断的修炼内功&#xff0c;使 Microsoft …

python发送邮件 退回_python 发送邮件(收到的邮件要有发送方才能回复)

Python使用SMTP(简单邮件传输协议)发送邮件普通文本邮件普通文本邮件发送的实现&#xff0c;关键是要将MIMEText中_subtype设置为plain## -*- coding: UTF-8 -*-import smtplibfrom email.mime.text import MIMEText#导入MIMEText类from email import encodersfrom email.heade…

JAVA面试常考系列六

转载自 JAVA面试常考系列六 题目一一个Applet有哪些生命周期&#xff1f; 一个Applet的生命周期分为以下四个阶段&#xff1a; Init 每次加载时都会初始化一个小程序。此方法通知Applet&#xff0c;方法已经被装入系统&#xff0c;在第一次调用start方法之前总是先调用它。Init…

.NET的未来包含一个开源的Mono

在微软Build 2016大会的第二天&#xff0c;微软项目经理Scott Hunter和Scott Hanselman就.NET平台的现状和未来计划做了一场演讲。演讲的题目是“.NET概述”&#xff0c;他们的精彩演讲耗时一个小时&#xff0c;描绘了公司对于.NET的目标以及开发人员可以期待什么。就像开幕式主…

kafka消费者接收分区测试

【README】 本文演示了当有新消费者加入组后&#xff0c;其他消费者接收分区情况&#xff1b;本文还模拟了 broker 宕机的情况&#xff1b;本文使用的是最新的 kafka3.0.0 &#xff1b;本文测试案例&#xff0c;来源于 消费者接收分区的5种模型&#xff0c;建议先看模型&#…

python数据分析架构_Python数据分析

引言&#xff1a;本文重点是用十分钟的时间帮读者建立Python数据分析的逻辑框架。其次&#xff0c;讲解“如何通过Python 函数或代码和统计学知识来实现数据分析”。本次介绍的建模框架图分为六大版块&#xff0c;依次为导入数据&#xff0c;数据探索&#xff0c;数据处理&…

JAVA面试常考系列七

转载自 JAVA面试常考系列七 题目一 Swing的方法中&#xff0c;有哪些是线程安全的&#xff1f; Swing的规则是&#xff1a;当Swing组件被具现化时&#xff0c;所有可能影响或依赖于组件状态的代码都应该在事件派发线程中执行。 因此有3个线程安全的方法&#xff1a; repaint()…

图片中的Build 2016

微软主办的Build 2016大会刚刚落幕&#xff0c;让我们通过下面的图片集锦来回顾大会的一些容易被人忽视的细节。 Xamarin加入微软大家庭 微软公司于二月底花大价钱买下了Xamarin这家移动开发平台提供商&#xff0c;终于补全了它Mobile First Cloud First战略的短板。 图片一&am…

diy实现spring依赖注入

【README】 本文diy代码实现了 spring 依赖注入&#xff0c;一定程度上揭示了依赖注入原理&#xff1b; 【1】控制反转-Inversion of Control 是一种编码思想&#xff0c;简而言之就是 应用程序A可以使用组件B&#xff0c;但A无法控制B的生命周期&#xff08;如创建&#xff…

html 中一个格子拆分成两个_一个效果惊人的数字游戏

安爸曾多次讲过数学推理能力对孩子成长的重要性&#xff0c;听到有位家长说自己用扔骰子的方法教孩子数学等式。步骤大致是扔骰子时&#xff0c;如果骰子是3&#xff0c;就在棋盘上从0出发走3步&#xff0c;并且写出033的加法等式。扔到负数就后退&#xff0c;写出减法等式。科…

JAVA面试常考系列八

转载自 JAVA面试常考系列八 题目一 JDBC是什么&#xff1f; JDBC&#xff08;Java DataBase Connectivity,java数据库连接&#xff09;是一种用于执行SQL语句的Java API&#xff0c;可以为多种关系数据库提供统一访问&#xff0c;由一组用Java语言编写的类和接口组成。JDBC提供…

【广州/深圳 活动】 MVP社区巡讲

紧跟当今的技术发展趋势还远远不够&#xff0c;我们要引领变革&#xff01;加入本地技术专家社区&#xff0c;获取真实案例、实况培训演示以及探讨新一代解决方案。在此活动中&#xff0c;您将&#xff1a; 了解如何运用开源&#xff08;OSS&#xff09;技术、Microsoft 技术及…

java socket实现简单即时通讯

【1】socket服务器 /*** Description 即时消息服务器* author xiao tang* version 1.0.0* createTime 2022年01月23日*/ public class IMSocketServer {private static int PORT 13;public static void main(String[] args) {ServerSocket server null;try {// 开启端口serv…