Linux进程休眠和唤醒_BruceZhang的博客-程序员秘密

技术标签: Unix  进程  Linux  唤醒  休眠  Linux内核设计与实现  

当进程以阻塞的方式通信,在得到结果前进程会挂起休眠。
为了将进程以一种安全的方式进入休眠,我们需要牢记两条规则:
一、永远不要在原子上下文中进入休眠。
二、进程休眠后,对环境一无所知。唤醒后,必须再次检查以确保我们等待的条件真正为真

简单休眠
完成唤醒任务的代码还必须能够找到我们的进程,这样才能唤醒休眠的进程。需要维护一个称为等待队列的数据结构。等待队列就是一个进程链表,其中包含了等待某个特定事件的所有进程。
linux维护一个“等待队列头”来管理,wait_queue_head_t,定义在<linux/wait.h>
struct  __wait_queue_head {
 wq_lock_t  lock;
 struct  list_head  task_list;
};
typedef  struct __wait_queue_head  wait_queue_head_t;
初始化方法:
静态方法:
DECLARE_WAIT_QUEUE_HEAD(name)
动态方法:
wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue);

linux中最简单的休眠方式是下面的宏, 
wait_event(queue, condition)  /*进程将被置于非中断休眠(uninterruptible sleep)*/
wait_event_interruptible(queue, condition) /*进程可被信号中断休眠,返回非0值表示休眠被信号中断*/
wait_event_timeout(queue, condition, timeout)    /*等待限定时间jiffy,condition满足其一返回0*/
wait_event_interruptible_timeout(queue, condition, timeout)
queue是等待队列头,传值方式
condition是任意一个布尔表达式,在休眠前后多次对condition求值,为真则唤醒

唤醒进程的基本函数是wake_up
void wake_up(wait_queue_head_t *queue);    /*唤醒等待在给定queue上的所有进程*/
void wake_up_interruptible(wait_queue_head_t *queue);

实践中,一般是wait_event和wake_up,wait_event_interruptible和wake_up_interruptible成对使用

高级休眠
将进程置于休眠的步骤:
(1)分配和初始化一个 wait_queue_t 结构, 随后将其添加到正确的等待队列

struct __wait_queue {        unsigned int flags;#define WQ_FLAG_EXCLUSIVE       0x01        void *private;        wait_queue_func_t func;        struct list_head task_list;};typedef struct __wait_queue wait_queue_t;(2)设置进程状态,标记为休眠。2.6内核中,使用下面的函数:
      void set_current_state(int new_state);
    在 <linux/sched.h> 中定义有几个任务状态:TASK_RUNNING 意思是进程能够运行。有 2 个状态指示一个进程是   在睡眠: TASK_INTERRUPTIBLE 和 TASK_UNTINTERRUPTIBLE

(3)最后一步是放弃处理器。 但必须先检查进入休眠的条件。如果不做检查会引入竞态: 如果在忙于上面的这个过程时有其他的线程刚刚试图唤醒你,你可能错过唤醒且长时间休眠。因此典型的代码下
if (!condition) 
     schedule( );    /*调用调度器,并让出CPU*/
如果代码只是从 schedule 返回,则进程处于TASK_RUNNING 状态。 如果不需睡眠而跳过对 schedule 的调用,必须将任务状态重置为 TASK_RUNNING,还必要从等待队列中去除这个进程,否则它可能被多次唤醒。

手工休眠
上面的进程休眠步骤可通过手工设置:
 (1)创建和初始化一个等待队列。常由宏定义完成:
DEFINE_WAIT(my_wait);
name 是等待队列入口项的名字. 也可以用2步来做:
wait_queue_t my_wait;
init_wait(&my_wait);
常用的做法是放一个 DEFINE_WAIT 在循环的顶部,来实现休眠

(2)添加等待队列入口到队列,并设置进程状态:
void prepare_to_wait(wait_queue_head_t *queue, 
                               wait_queue_t *wait, 
                               int state); 
queue 和 wait 分别地是等待队列头和进程入口。state 是进程的新状态:TASK_INTERRUPTIBLE(可中断休眠,推荐)或TASK_UNINTERRUPTIBLE(不可中断休眠,不推荐)

(3)在检查确认仍然需要休眠之后调用 schedule
if (!condition) 
     schedule( );    /*调用调度器,并让出CPU*/

(4)schedule 返回,就到了清理时间:
void finish_wait(wait_queue_head_t *queue, wait_queue_t *wait);

认真地看简单休眠中的 wait_event(queue, condition) 和 wait_event_interruptible(queue, condition) 底层源码会发现,其实他们只是手工休眠中的函数的组合。所以怕麻烦的话还是用wait_event比较好。

独占等待
当一个进程调用 wake_up 在等待队列上,所有的在这个队列上等待的进程被置为可运行的。 这在许多情况下是正确的做法。但有时,可能只有一个被唤醒的进程将成功获得需要的资源,而其余的将再次休眠。这时如果等待队列中的进程数目大,这可能严重降低系统性能。为此,内核开发者增加了一个“独占等待”选项。它与一个正常的睡眠有 2 个重要的不同:
(1)当等待队列入口设置了 WQ_FLAG_EXCLUSEVE 标志,它被添加到等待队列的尾部;否则,添加到头部。
(2)当 wake_up 被在一个等待队列上调用, 它在唤醒第一个有 WQ_FLAG_EXCLUSIVE 标志的进程后停止唤醒.但内核仍然每次唤醒所有的非独占等待。
采用独占等待要满足 2 个条件:
(1)希望对资源进行有效竞争;
(2)当资源可用时,唤醒一个进程就足够来完全消耗资源。
使一个进程进入独占等待,可调用: 
void prepare_to_wait_exclusive(wait_queue_head_t *queue, wait_queue_t *wait, int state); 
注意:无法使用 wait_event 和它的变体来进行独占等待.

唤醒函数
很少会需要调用wake_up_interruptible 之外的唤醒函数,但为完整起见,这里是整个集合:
wake_up(wait_queue_head_t *queue); 
wake_up_interruptible(wait_queue_head_t *queue); 
wake_up 唤醒队列中的每个非独占等待进程和一个独占等待进程。wake_up_interruptible 同样, 除了它跳过处于不可中断休眠的进程。它们在返回之前, 使一个或多个进程被唤醒、被调度(如果它们被从一个原子上下文调用, 这就不会发生).
wake_up_nr(wait_queue_head_t *queue, int nr); 
wake_up_interruptible_nr(wait_queue_head_t *queue, int nr); 
这些函数类似 wake_up, 除了它们能够唤醒多达 nr 个独占等待者, 而不只是一个. 注意传递 0 被解释为请求所有的互斥等待者都被唤醒
wake_up_all(wait_queue_head_t *queue); 
wake_up_interruptible_all(wait_queue_head_t *queue); 
这种 wake_up 唤醒所有的进程, 不管它们是否进行独占等待(可中断的类型仍然跳过在做不可中断等待的进程)
wake_up_interruptible_sync(wait_queue_head_t *queue); 
一个被唤醒的进程可能抢占当前进程, 并且在 wake_up 返回之前被调度到处理器。 但是, 如果你需要不要被调度出处理器时,可以使用 wake_up_interruptible 的"同步"变体. 这个函数最常用在调用者首先要完成剩下的少量工作,且不希望被调度出处理器时。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/DLUTBruceZhang/article/details/8695224

智能推荐

linux中C语言函数:字符串处理函数_haoshenwang的博客-程序员秘密

所有函数的头文件都为bcmp :比较两个内存中的内容bcopy : 复制内存中的内容bzero : 将一个内存内容全清零ffs : 在一个整数中查找第一个值为真的位index : 查找字符串中第一个出现的指定字符memccpy :复制内存中的内容memchr :在一块内存指定范围内查找一个指定字符memcmp :比较内存中存放的内容memcpy : 复制一块内存内容到另一块memfrob

Maven主仓库中找不到包的解决办法(如org.restlet.jee not found)_iteye_4921的博客-程序员秘密

有时候,通过maven总是找不到某些包,比如今天我就碰到 Could not find artifact org.restlet.jee:org.restlet:jar:2.3.0 , 这时候可以通过如下的方法去解决: 1、可以找到restlet的官方repo,将其加到工程的pom中:&amp;lt;repositories&amp;gt; &amp;lt;repository&amp;...

Android系统匿名共享内存Ashmem(Anonymous Shared Memory)驱动程序源代码分析_罗升阳的博客-程序员秘密

在上一文章Android系统匿名共享内存Ashmem(Anonymous Shared Memory)简要介绍和学习计划中,我们简要介绍了Android系统的匿名共享内存机制,其中,简要提到了它具有辅助内存管理系统来有效地管理内存的特点,但是没有进一步去了解它是如何实现的。在本文中,我们将通过分析Android系统的匿名共享内存Ashmem驱动程序的源代码,来深入了解它是如何辅助内存管理系

SP_attach_db/数据库移植_mrpanwei的博客-程序员秘密

SP_attach_db   用法如下:      EXEC   SP_attach_db   @dbname  =   N'目标数据库名',    //这是你要引入后的数据库名。   @filename1   =   N'源数据库DATA文件完整路径及文件名.MDF',   //指明源数据库的数据文件    @filename2   =   N'源数据库LOG文件完整路径及文件名

JavaWeb项目中图片URL中文乱码问题解决方法_猛浩的博客-程序员秘密

我们在编写图片上传时候有可能会上传一张带有中文名的URL,但是一般情况下,上传之后调用的话是会产生编码错误的问题。网上有一些说法,可以使用URLEncoding.parse(url)或者使用JavaScript的编解码方法,试了之后没有什么效果。 后来看到一篇博客介绍了解决方法,在此做个记录。 其实这种方法主要就是修改tomcat的配置文件,使tomcat服务器可以解析中文编码。废话不多说上代...

学习C++ primer 之路 - ( 第十六章 之 STL模板之容器的种类)_stl对应primer章节_kanxue1.0的博客-程序员秘密

一、STL容器种类STL具有容器的概念和容器类型的。其中具有名称(如容器,序列容器和关联容器等等)的通用类别容器类型。C++11之前公有11个类型的容器,分别为:deque、list、priority_queue、stack、vector、map、multimap、set、multiset和bitsetC++11之后新增加了forword_list(单向链表容器)、unordered_...

随便推点

datetime.timedelta & datetime.datetime_littleflea_的博客-程序员秘密

&amp;gt;&amp;gt;&amp;gt; from datetime import timedelta&amp;gt;&amp;gt;&amp;gt; a = timedelta(days=2,hours=6)&amp;gt;&amp;gt;&amp;gt; b = timedelta(hours = 4.5)&amp;gt;&amp;gt;&amp;gt; c = a+b&amp;gt;&amp;gt;&amp;gt; c.days2&amp;gt;&amp;gt;&amp;gt; c.hours

vue 解决移动端滚动穿透_夏颜末的博客-程序员秘密

弹框滑动穿透问题问题描述在组件A里面添加了弹框,A页面是列表,弹框里面也是列表,当弹框里面的内容超过页面高度时弹框出现滑动,随之A页面也出现了滑动,解决方法如下:在弹窗打开的时候给body的全局滚动设置position:fixed属性,并设置top值;由于设置了fixed属性,那在弹窗的时候body就没有滚动条了。此时如果这么设置会发现body虽然没有了滚动穿透,但是原来的位置丢失了。所以再给body设置fixed属性的时候,要把当前的滚动位置赋值给css的top属性,那在视觉上就没有任何变化了。

微信小程序:弹窗_极客点儿的博客-程序员秘密

微信小程序在和用户交互时候需要给用户一些提示,例如是否请求到数据等,这时候使用弹窗是一个比较好的方法好在微信小程序给我们提供了一个 API wx.showToastsuccess: function (res) { app.globalData.allData = res.data.datas; if (res.data.status == 200) { ...

京东面试真题解析,小白也能看明白_普通网友的博客-程序员秘密

我的移动开发春季历程没有稳定的工作,只有稳定的能力。春天,又到了万物复苏的季节,在程序猿这个行当里,作为 Android 开发出生的我,在经历了5年的脱发生涯后,现在更多的是称呼自己为移动开发攻城狮。正文面试总共花费30天左右,才拿到了offer。一面1.自我介绍2.项目3.四大组件4.activity生命周期5.启动模式6.线程状态7.网络协议(每一层、还有TCP和UDP)8.会不会网络编程9.handler10.JVM,内存模型那些11.GC(有哪些方法那种问题)1

Unity3d常见问题 VisualStudio找不到.sln_unity打开脚本显示没有.sln文件无法加载_真像大白阿的博客-程序员秘密

问题描述:使用Unity2018新建一个项目,新建脚本,报错:命令行中执行了一下文件:..\project.sln,未能找到这些文件,将无法加载。原因:看项目是否使用的是Visual Studio 2017或者Visual Studio2019,从Unity2018开始,不再支持Visual Studio 2018,为了解决这个问题,可以下载VS2017或者VS2019版本,或者下...

推荐文章

热门文章

相关标签