Linux0.11内核 进程睡眠和唤醒

2023-05-16

      当进程等待资源或者事件时,就进入睡眠状态。有两种睡眠态,不可中断睡眠态( TASK_UNINTERRUPTIBLE)和可中断睡眠态(TASK_INTERRUPTIBLE)。

处于可中断睡眠态的进程不光可以由 wake_up直接唤醒,还可以由信号唤醒。在 schedule()函数中,会把处于可中断睡眠态并且收到信号的进程变成运行态,使他参与调度选择。 Linux0.11中进入可中断睡眠状态的方法有 3

  1. 调用 interruptible_sleep_on()函数

  2. 调用 sys_pause()函数

  3. 调用 sys_waitpid()函数。

第一种情况用于等待外设资源时(如等待 I/O设备),这时当前进程会挂在对应的等待队列上。第二第三种情况用于事件,即等待信号。

进程要进入不可中断睡眠态,只能通过 sleep_on()函数。要使处于不可中断睡眠态的进程进入运行态,只能由其他进程调用 wake_up()将它唤醒。当进程等待系统资源(比如高速缓冲块,文件 i节点或者文件系统的超级块)时,会调用 sleep_on()函数,使当前进程挂起在相关资源的等待队列上。

这部分代码很短,一共三个函数 sleep_on() wake_up() interruptible_sleep_on()。在 sched.c中。但是代码比较难理解,因为构造的等待队列是一个隐式队列,利用进程地址空间的独立性隐式地连接成一个队列。这个想法很奇妙。

sleep_on()

[cpp] view plaincopy
  1. /****************************************************************************/  
  2. /* 功能:当前进程进入不可中断睡眠态,挂起在等待队列上                        */  
  3. /* 参数:p 等待队列头                                                       */  
  4. /* 返回:(无)                                                               */  
  5. /****************************************************************************/  
  6. void sleep_on(struct task_struct **p)  
  7. {  
  8.     struct task_struct *tmp;        // tmp用来指向等待队列上的下一个进程  
  9.     if (!p)         // 无效指针,退出  
  10.         return;  
  11.     if (current == &(init_task.task))   // 进程0不能睡眠  
  12.         panic("task[0] trying to sleep");  
  13.     tmp = *p;           // 下面两句把当前进程放到等待队列头,等待队列是以堆栈方式  
  14.     *p = current;       //  管理的。后到的进程等在前面  
  15.     current->state = TASK_UNINTERRUPTIBLE;   // 进程进入不可中断睡眠状态  
  16.     schedule();     // 进程放弃CPU使用权,重新调度进程  
  17. // 当前进程被wake_up()唤醒后,从这里开始运行。  
  18. // 既然等待的资源可以用了,就应该唤醒等待队列上的所有进程,让它们再次争夺  
  19. // 资源的使用权。这里让队列里的下一个进程也进入运行态。这样当这个进程运行  
  20. // 时,它又会唤醒下下个进程。最终唤醒所有进程。  
  21.     if (tmp)      
  22.         tmp->state=0;  
  23. }  

 

这个函数牵涉到 3个指针, p tmp current

p是指向指针的指针,实际上 *p指向的是等待队列头。系统资源(高速缓冲块,文件 i节点或者文件系统的超级块)的数据结构中都一个struct task_struct *类型的指针,指向的就是等待该资源的进程队列头。比如 i节点中的 i_wait,高速缓冲块中的 b_wait,超级块中的s_wait *p对于等待队列上的所有进程都是一样的。

current指向的是当前进程指针,是全局变量。

tmp位于当前进程的地址空间内,是局部变量。不同的进程有不同 tmp变量。等待队列就是利用这个变量把所有等待同一个资源的进程连接起来。具体的说,所有等待在队列上的进程,都是在 sleep_on() schedule()中被切换出去的,这些进程还停留在 sleep_on()函数中,在函数的堆栈空间里面,存放了局部变量 tmp

假如当前进程要进入某个高速缓冲块的等待队列,而且该等待队列上已经有另外两个进程 task1 task2先后进入。形成的队列如图。等待队列是堆栈式的,先进入队列的进程排在最后。

在调用了 sleep_on()的地方,我们可以发现 sleep_on()往往是放在一个循环中的(比如 wait_on_buffer() wait_on_inode() lock_inode()lock_super() wait_on_super()等函数)。当进程从 sleep_on()返回时,并不能保证当前进程取得了资源使用权,因为调用 wake_up()进程切换到从 sleep_on()中苏醒的过程中,发生了进程调度,中间很可能有别的进程取得了资源。

 

wake_up()

[cpp] view plaincopy
  1. /****************************************************************************/  
  2. /* 功能:唤醒等待队列上的头一个进程                                         */  
  3. /* 参数:p 等待队列头                                                       */  
  4. /* 返回:(无)                                                               */  
  5. /****************************************************************************/  
  6. void wake_up(struct task_struct **p)  
  7. {  
  8.     if (p && *p) {  
  9.         (**p).state=0;      // 把队列上的第一个进程设为运行态  
  10.         *p=NULL;        // 把队列头指针清空,这样失去了都其他等待进程的跟踪。  
  11.                         //  一般情况下这些进程迟早会得到运行。  
  12.     }  
  13. }  

 

下面分析 sleep_on() 和 wait_up()配合使用的情况

情况一 游离队列的产生

先分析一下 sleep_on() wake_up()在通常情况下的工作原理。考虑一个非常简单的情况,假设目前系统只有 3个进程,且都等在队列上,队列的头指针设为 wait

然后系统资源得到释放,当前进程调用 wake_up(wait)。这时 Task C变成了运行态。

之后进程调度发生, Task C被选中,开始运行。 Task C是从 sheep_on()中的 schedule()的后一条语句开始运行,它把 Task B的状态变成运行态。随后 Task C退出 sheep_on()函数,堆栈中的局部变量 tmp消失,这样再没有指向 Task B的指针, Task B开头的队列游离了。

情况 1-1

这时对同一个资源有两个进程是可运行状态,但是当前进程是 Task C,只要它不调用 schedule,它是不会被抢断的。因此 Task C继续运行,取得了它想要的资源,这时 Task C可以完成它的任务了。当进程调度再次发生时, Task B会被选中,同样, Task B会把 Task A变成可运行态,而它自己得到了资源。最终 Task A也会得到执行。这样,等待在一个资源上的三个任务最终都得到运行。

情况 1-2

假设 Task C在得到资源后,又主动调用了 schedule(),进程调度程序这时选中了 Task B Task B从上次中断的地方开始运行,即从sleep_on() schedule()后面的语句开始运行。它会把 Task A也变成可运行状态。然后退出 sleep_on() tmp变量消失。但是不幸的是它发现资源仍然被占用,所以再次进入睡眠,又连接到 wait队列上了。

从这个情况可以看到,虽然系统运行过程中,可能会把等待队列切分成很多游离队列,但是这些队列头上的进程都是运行态,这保证schedule()函数最终还是会找到它。

 

 

情况二 游离队列的合并

假设目前进程等待资源的情况如下,某个进程占用资源不放,导致有 7个进程等待该资源。产生 3个队列,其中两个游离。

这时调度函数选中 Task E执行, Task E先唤醒 Task D但发现资源不能用,再次睡眠,把自己移到 wait队列,脱离了游离队列。调度再次发生。

假如这时 Task B得到运行,同样 Task B也只能唤醒 Task A,而把自己移动到等待队列


这样,只要游离队列头上的进程是运行态,游离队列可以再次合并到原先的等待队列上。


0.11版这段代码是有问题的,wake_up时应该将所有等待进程一次唤醒,否则会有race condition。

一次唤醒所有等待进程然后按优先级等调度下一个运行进程才是合理的调度,这里每次都是最后sleep的任务最先唤醒,不合理。

另外interrruptible_sleep_on中signal唤醒的进程会唤醒其他同一队列的进程,这不合理。


interruptible_sleep_on()

 

[cpp]  view plain copy
  1. /****************************************************************************/  
  2. /* 功能:当前进程进入可中断睡眠态,挂起在等待队列上                         */  
  3. /* 参数:p 等待队列头                                                       */  
  4. /* 返回:(无)                                                               */  
  5. /****************************************************************************/  
  6. void interruptible_sleep_on(struct task_struct **p)  
  7. {  
  8.     struct task_struct *tmp;    // tmp用来指向等待队列上的下一个进程  
  9.       
  10.     if (!p) // 无效指针,退出  
  11.         return;  
  12.     if (current == &(init_task.task))   // 进程0不能睡眠  
  13.         panic("task[0] trying to sleep");  
  14.     tmp=*p;     // 和sleep_on()一样,构建隐式队列  
  15.     *p=current;  
  16. repeat: current->state = TASK_INTERRUPTIBLE; // 当前进程状态变成可中断睡眠态  
  17.     schedule();     // 重新调度进程  
  18. // 当进程苏醒后,从这里继续运行  
  19.     if (*p && *p != current) {  // 如果当前进程之前还有进程,这把头进程唤醒,  
  20.         (**p).state=0;          // 自己进入睡眠态。这样做为了保证队列栈式管理  
  21.         goto repeat;  //这个判断语句将一直进行下去 导致进程切换 直到切换至等待队列头 从而导致和可中断函数sleep_on()一样的效果
  22.     }  
  23.     *p=NULL;    // 和wake_up()一样  
  24.     if (tmp)        // 产生了游离队列,需要把头进程唤醒  
  25.         tmp->state=0;  
  26. }
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux0.11内核 进程睡眠和唤醒 的相关文章

  • Shell脚本学习——字符串处理

    最近在做很多的字符串处理的工作 xff0c 主要用的还是excel xff0c 但是很费神 xff0c 总是Ctrl 43 C和Ctrl 43 V 就想用shell脚本对对应的字符串进行批量处理 1 xff09 首先是读取文件中的每一行字符
  • DSP算法移植---总结

    总结 很久没有写文章了 xff0c 今天逛了逛博客 xff0c 看到DSP算法移植的文章 xff0c 至于这个项目 xff0c 本人解释下 本人7月份将硕士毕业了 xff0c 不久就要离开校园了 xff0c 所以我基本上不接触这个项目了 x
  • L2TP协议【转载】

    L2TP协议 一 43 L2TP Layer 2 Tunnel Protocol 第2层隧道协议 xff0c 是为在用户和企业的服务器之间透明传输PPP报文而设置的隧道协议 43 特性 灵活的身份验证机制以及高度的安全性 多协议传输 支持R
  • ROMFS文件系统分析

    ROMFS文件系统分析 ROMFS是一种简单的只读文件系统 xff0c 主要是用来当做初始文件系统来使用的 xff0c 在嵌入式linux或是uclinux中通常使用这中文件系统来作为引导系统的文件系统 xff0c 甚至uclinux有时就
  • Linux/Ubuntu 怎么设置打开远程桌面登录连接

    Liux Ubuntu 怎么设置打开远程桌面登录连接 Linux Ubuntu 下做一些设置在windows中就可以打开远程桌面登录Ubuntu 或者在本地Ubuntu远程登录windows或者Ubuntu远程登录别的Ubuntu桌面 xf
  • Latex论文复杂组合图片设计

    最近在使用Latex写博士论文 xff0c 中间遇到了一些想设计一幅图片中多个子图位置的问题 如果想画普通的横排多图或者竖排多图 xff0c 可以使用 begin figure centering subfigure includegrap
  • 打麻将清一色自摸后偏瘫?高血压患者这些事项要注意

    最近有一条让人哭笑不得的热搜 xff0c 上海一男子居然因为打麻将清一色自摸兴奋过度 xff0c 导致脑出血偏瘫了 细究原因 xff0c 是因为该男子平时就有高血压 xff0c 又爱打麻将 对此医生也提醒 xff0c 情绪过于激动时极易诱发
  • 对idea2020.1.1的遇坑实测【实时更新】

    1 第一坑 xff0c 你可以更换maven库 xff0c 但是不管你怎么换 xff0c idea2020 1 1还是会使用 m下的 虽然你在project structrue里看起来都很正常 xff0c 但是你一打包运行就会发现你的art
  • 在Ubuntu系统中编译C语言程序

    在Ubuntu系统中编译C语言程序 用gcc命令编译主程序main1 c 并运行在VC 43 43 6 0中编译主程序main1 c 并运行在ubuntu系统下用Makefile的方式编译主程序 用gcc命令编译主程序main1 c 并运行
  • STM32简单程序的编译——LED闪烁

    LED闪烁 程序编译总结 程序编译 默认已经完成MDK5软件和STM32包的安装 首先利用keil建立工程 打开Keil uVision5软件 xff0c 点击左上角的File xff0c 然后点击New建立新文件 然后点击Project选
  • Ubuntu下opencv的安装使用

    目录 一 Ubuntu下安装opencv二 Ubuntu下opencv摄像头的使用1 打开摄像头显示处理视频的程序2 改进 三 总结 一 Ubuntu下安装opencv 1 下载oencv 在ubuntu的浏览器中输入官网链接 https
  • Proteus仿真运行stm32程序

    目录 一 原理图二 keil程序编译三 proteus仿真四 总结 一 原理图 1 创建工程 打开proteus xff0c 选择新建工程 编辑名称和目录 xff0c 点击下一步 选择下图模板 选择创建固件项目 xff0c 系列选择Cort
  • 框式路由器MPU、SFU、LPU、SRU科普

    MPU xff08 Main Processing Unit xff09 主控单元SFU xff08 Switch Fabric Unite xff09 交换网板LPU xff08 Line Processing Unit xff09 线路
  • 在STM32下完成基于FreeRTOS的多任务程序

    目录 一 任务要求二 FreeRTOS移植1 简单介绍2 应用实例 一 任务要求 在STM32下完成一个基于FreeRTOS的多任务程序 xff0c 执行3个周期性task task1 xff0c 每间隔500ms闪烁 xff08 变化 x
  • 学习HTML后总结

    目录 一 HTML简介二 软件 插件使用三 HTML的文档结构1 元素2 文本格式3 标题4 超链接5 图片 文件路径img6 表格7 列表List8 表单Form9 其他内容 一 HTML简介 HTML是超文本标记语言 xff08 Hyp
  • 学习CSS后总结

    目录 一 CSS简介二 CSS语法三 CSS的生效方式四 CSS实际使用1 颜色 尺寸 对齐2 盒子模型3 边框与边距4 定位5 溢出6 浮动7 透明度8 组合选择器9 伪类和伪元素 一 CSS简介 CSS是级联样式表 xff08 Casc
  • 《通信软件开发与应用》课程结业报告

    目录 一 作业内容二 完成过程三 问题及解决办法四 仍然存在的问题五 总结 一 作业内容 要求 xff1a 完成一个静态网站的制作 可使用你喜欢的任何CSS框架如BootStrap MDB tailwind等 xff0c 页面不少于5个 x
  • prometheus + grafana 监控Mysql、主机、Redis超简入门

    文章目录 grafana安装启动如何导入需要的dashboard prometheus安装集成grafanaExportermysqld exporter安装配置 node exporterredis exporter grafana 安装
  • 地址栏中输入IP地址提示指定的网络名不再可用的解决方法

    在日常工作中 xff0c 我们有的时候为了省去在网上邻居查找相关资料的麻烦 xff0c 一般都直接在地址栏中输入远程地址 xff0c 如192 168 1 100 F xff0c 可有的时候偏偏输入地址后却提示 指定的网络名不再可用 的问题
  • ubuntu18.04添加用户

    文章目录 前言一 创建用户目录二 修改用户权限 前言 笔记 在远程服务的时候经常遇到用户登入冲突的情况 xff0c 在此做了总结 一 创建用户目录 span class token comment 创建deepstream user spa

随机推荐

  • 详细介绍如何在ROS 中使用自定义 msg 消息

    首先我们对Ros中的msg 消息做以简单的回顾 xff1a msg 消息是 ROS topic 通讯时 xff0c 节点之间进行传递的内容 msg 消息描述的是业务之间传递数据 xff0c 也可以理解为业务之间数据的抽象化 常见 Msg 消
  • linux下的信号量操作示例

    include lt sys types h gt include lt sys ipc h gt include lt sys sem h gt include lt pthread h gt include lt errno h gt
  • 用户行为分析的指标及其意义

    什么是用户行为 xff1f 用户行为由最简单的五个元素构成 xff0c 我们可以简单地记为3W2H xff1a 时间 xff08 When xff09 地点 xff08 Where xff09 人物 xff08 Who xff09 交互 x
  • 二层漫游和三层漫游(转载)

    漫游域 根据STA是否在同一个子网内漫游 xff0c 可以将漫游分为二层漫游和三层漫游 不同子网之间的漫游是三层漫游 xff08 同一个VLAN pool内的漫游仍然属于二层漫游 xff09 网络中有时候会出现以下情况 xff1a 两个子网
  • HTML DOM 事件

    1 HTML DOM 事件 参考文档 xff1a https www runoob com jsref dom obj event html HTML DOM 事件允许Javascript在HTML文档元素中注册不同事件处理程序 事件通常与
  • 用户分类以及用户活跃度的衡量方法

    一个APP最根本的便是用户 xff0c 那么当然用户也分许多种类 xff0c 比如活跃用户 留存用户 流失用户等等 xff0c 那么一般情况下又是怎么来分析活跃用户的呢 xff1f 首先 xff0c 我们来看一下用户的具体分类 不同类型的用
  • 什么是用户标签?其实很好理解

    用户画像与用户标签 亚马逊的CEO Jeff Bezos曾说过他的梦想 xff0c 如果我有一百万的用户 xff0c 我就会做一百万个不同的网站 xff01 xff0c 做这个的基础是先对用户打标签 什么是用户画像呢 xff1f 就是根据某
  • 用户标签体系的意义及设计方法

    我们这次重点谈一下用户标签 对于市场层面 xff0c 用户标签能帮助我们什么 xff1f 1 完善数据仓 之前我们讲过 xff0c 企业或市场要有自己的数据仓来进行线索的存储与培育 xff0c 用户标签就是存在于此 xff0c 我们希望用户
  • 如何设计数据埋点方案?知道这2步就行了

    数据埋点是什么 xff1f 数据埋点是数据产品经理 数据运营以及数据分析师 xff0c 基于业务需求 xff08 例如 xff1a CPC点击付费广告中统计每一个广告位的点击次数 xff09 xff0c 产品需求 xff08 例如 xff1
  • 什么是UTM参数?这些你知道吗

    现在移动互联网发展比较迅速 xff0c 而且现在很多人都在做公众号 xff0c 公众号也要有自己的特色 xff0c 这样才能给自己的产品带来利益 现在也有很多的人关注APP运营 xff0c APP运营工作中的每一步都会讲求ROI xff08
  • 用户触达方式及用户触达渠道选择

    任何用户运营过程总离不开用户触达渠道的连接 可以说 xff0c 触达渠道的组合选择 xff0c 是与你最终运营效果直接挂钩的 xff0c 用户触达方式的选择直接影响了你运营的结果 如何做精准的用户触达 如何选择不同的用户触达方式 如何最大限
  • 用户行为分析之渠道分析、转化分析、留存分析

    数据分析脱离不了业务 xff0c 不同的业务所关注的数据不同 xff0c 比如互联网 快消等 xff0c 行业不同 xff0c 关注的数据点也不同 在互联网行业普遍产品的数据分析中 xff0c 我认为用户行为分析最重要的三个点是渠道分析 转
  • 通过用户分级实现精细化运营

    10年前 xff0c pc互联网时代 xff0c 当你浏览百度的网页 xff0c 你会普遍看到各种插件推广 弹窗广告等等 xff0c 这些弹窗就好似牛皮癣一样 xff0c 想关掉都不行 用户体验极其不好 xff0c 这是一个 卖方 占绝对优
  • 如何进行流失用户召回?做到这三步!

    如果按照每天渠道投放获客1000名 xff0c 次日留存率40 来算 xff0c 每天会有60 的用户 xff0c 第二天就再也不打开我们的APP xff0c 最终成为了流失用户 平均每日损失几百到数万元不等 虽然相比动辄几百万到几千万的融
  • Spring使用到的设计模式

    Spring涉及到的设计模式 简单工厂模式工厂模式单例模式适配器装饰器模式 Decortor代理模式观察者模式策略模式模板模式 简单工厂模式 一个工厂类根据传入的参数 xff0c 动态决定创建哪一个类 public abstract cla
  • 路由协议的优先级

    对于相同的目的地 xff0c 不同的路由协议 xff08 包括静态路由 xff09 可能会发现不同的路由 xff0c 但这些路由并不都是最优的 事实上 xff0c 在某一时刻 xff0c 到某一目的地的当前路由仅能由唯一的路由协议来决定 为
  • 自己动手写操作系统 将引导程序成功写入优盘启动电脑

    原文 xff1a http freesoftman iteye com blog 629598 输入命令 xff1a nasm boot asm o boot bin 一会儿就生成了一个镜像文件boot bin 该文件就是我所谓的操作系统了
  • 关于C语言等高级语言能不能直接控制硬件的问题

    关于C语言等高级语言能不能直接控制硬件的问题 xff0c 我认为C语言等高级语言不能直接控制硬件 这里谈论的问题本质是 xff0c C语言等高级语言能不能直接对硬件进行编程 我认为 xff0c 不能 众所周知 xff0c 计算机之初的程序员
  • scanf函数输入字符 %c之前要有空格分析

    问题描述如下 xff1a test c int main void int n 61 0 char c while 1 scanf 34 c 34 amp c printf 34 c d n 34 c 43 43 i return 0 这段
  • Linux0.11内核 进程睡眠和唤醒

    当进程等待资源或者事件时 xff0c 就进入睡眠状态 有两种睡眠态 xff0c 不可中断睡眠态 xff08 TASK UNINTERRUPTIBLE xff09 和可中断睡眠态 xff08 TASK INTERRUPTIBLE xff09