Linux 进程的睡眠和唤醒详解

news2024/11/25 21:34:30


概要 

在Linux中,仅等待CPU时间的进程称为就绪进程,它们被放置在一个运行队列中,一个就绪进程的状 态标志位为 TASK_RUNNING。一旦一个运行中的进程时间片用完, Linux 内核的调度器会剥夺这个进程对CPU的控制权,并且从运行队列中选择一个合适的进程投入运行。

当然,一个进程也可以主动释放CPU的控制权。函数 schedule() 是一个调度函数,它可以被一个进程主动调用,从而调度其它进程占用 CPU。一旦这个主动放弃 CPU 的进程被重新调度占用 CPU,那么它将从上次停止执行的位置开始执行,也就是说它将从调用 schedule() 的下一行代码处开始执行。

有时候,进程需要等待直到某个特定的事件发生,例如设备初始化完成、I/O 操作完成或定时器到时等。在这种情况下,进程则必须从运行队列移出,加入到一个等待队列中,这个时候进程就进入了睡眠状态。

Linux 中的进程睡眠状态有两种:

一种是可中断的睡眠状态,其状态标志位TASK_INTERRUPTIBLE

另一种是不可中断 的睡眠状态,其状态标志位为TASK_UNINTERRUPTIBLE

可中断的睡眠状态的进程会睡眠直到某个条件变为真,比如说产生一个硬件中断、释放 进程正在等待的系统资源或是传递一个信号都可以是唤醒进程的条件。不可中断睡眠状态与可中断睡眠状态类似,但是它有一个例外,那就是把信号传递到这种睡眠 状态的进程不能改变它的状态,也就是说它不响应信号的唤醒。不可中断睡眠状态一般较少用到,但在一些特定情况下这种状态还是很有用的,比如说:进程必须等 待,不能被中断,直到某个特定的事件发生。

在现代的 Linux 操作系统中,进程一般都是用调用 schedule() 的方法进入睡眠状态的,下面的代码演示了如何让正在运行的进程进入睡眠状态。微信搜索公众号:信安黑客技术,回复:黑客 领取资料 。

sleeping_task = current;

set_current_state(TASK_INTERRUPTIBLE);

schedule();

func1();

/* Rest of the code ... */

在第一个语句中,程序存储了一份进程结构指针 sleeping_taskcurrent 是一个宏,它指向正在执行的进程结构。

set_current_state() 将该进程的状态从执行状态 TASK_RUNNING 变成睡眠状态 TASK_INTERRUPTIBLE。如果 schedule() 是被一个状态为 TASK_RUNNING 的进程调度,那么 schedule() 将调度另外一个进程占用CPU。

如果 schedule() 是被一个状态为 TASK_INTERRUPTIBLE  TASK_UNINTERRUPTIBLE 的进程调度,那么还有一个附加的步骤将被执行:当前执行的进程在另外一个进程被调度之前会被从运行队列中移出,这将导致正在运行的那个进程进入睡眠,因为它已经不在运行队列中了。

我们可以使用下面的这个函数将刚才那个进入睡眠的进程唤醒。

wake_up_process(sleeping_task);

调用了 wake_up_process() 以后,这个睡眠进程的状态会被设置为 TASK_RUNNING,而且调度器会把它加入到运行队列中去。当然,这个进程只有在下次被调度器调度到的时候才能真正地投入运行。

无效唤醒

几乎在所有的情况下,进程都会在检查了某些条件之后,发现条件不满足才进入睡眠。可是有的时候进程却会在判定条件为真后开始睡眠,如果这样的话进程就会无限期地休眠下去,这就是所谓的无效唤醒问题。

在操作系统中,当多个进程都企图对共享数据进行某种处理,而 最后的结果又取决于进程运行的顺序时,就会发生竞争条件,这是操作系统中一个典型的问题,无效唤醒恰恰就是由于竞争条件导致的。

设想有两个进程A BA 进程正在处理一个链表,它需要检查这个链表是否为空,如果不空就对链表里面的数据进行一些操作,同时B进程也在往这个链表添加节点。当这个链表是空的时候,由于无数据可操作,这时A进程就进入睡眠,当B进程向链表里面添加了节点之后它就唤醒A 进程,其代码如下:

A进程:

1 spin_lock(&list_lock);

2 if (list_empty(&list_head)) {

3     spin_unlock(&list_lock);

4     set_current_state(TASK_INTERRUPTIBLE);

5     schedule();

6     spin_lock(&list_lock);

7 }

8

9 /* Rest of the code ... */

10 spin_unlock(&list_lock);

B进程:

100 spin_lock(&list_lock);

101 list_add_tail(&list_head, new_node);

102 spin_unlock(&list_lock);

103 wake_up_process(processa_task);

这里会出现一个问题,假如当A进程执行到第3行后第4行前的时候,B进程被另外一个处理器调度投入运行。在这个时间片内,B进程执行完了它所有的指令,因此它试图唤醒A进程,而此时的A进程还没有进入睡眠,所以唤醒操作无效。

这之后,A 进程继续执行,它会错误地认为这个时候链表仍然是空的,于是将自己的状态设置为 TASK_INTERRUPTIBLE 然后调用 schedule() 进入睡 眠。由于错过了B进程唤醒,它将会无限期的睡眠下去,这就是无效唤醒问题,因为即使链表中有数据需要处理,A 进程也还是睡眠了。

避免无效唤醒

如何避免无效唤醒问题呢?

我们发现无效唤醒主要发生在检查条件之后和进程状态被设置为睡眠状态之前,本来B进程 wake_up_process() 提供了一次将A进程状态置为 TASK_RUNNING 的机会,可惜这个时候A进程的状态仍然是 TASK_RUNNING,所以 wake_up_process() 将A进程状态从睡眠状态转变为运行状态的努力 没有起到预期的作用。

要解决这个问题,必须使用一种保障机制使得判断链表为空和设置进程状态为睡眠状态成为一个不可分割的步骤才行,也就是必须消除竞争条 件产生的根源,这样在这之后出现的 wake_up_process() 就可以起到唤醒状态是睡眠状态的进程的作用了。

找到了原因后,重新设计一下A进程的代码结构,就可以避免上面例子中的无效唤醒问题了。

A进程:

1 set_current_state(TASK_INTERRUPTIBLE);

2 spin_lock(&list_lock);

3 if (list_empty(&list_head)) {

4     spin_unlock(&list_lock);

5     schedule();

6     spin_lock(&list_lock);

7 }

8 set_current_state(TASK_RUNNING);

9

10 /* Rest of the code ... */

11 spin_unlock(&list_lock);

可以看到,这段代码在测试条件之前就将当前执行进程状态转设置成 TASK_INTERRUPTIBLE 了,并且在链表不为空的情况下又将自己置为 TASK_RUNNING 状态。

这样一来如果B进程在A进程进程检查了链表为空以后调用 wake_up_process(),那么A进程的状态就会自动由原来 TASK_INTERRUPTIBLE 变成 TASK_RUNNING,此后即使进程又调用了 schedule(),由于它现在的状态是 TASK_RUNNING,所以仍然不会被从运行队列中移出,因而不会错误的进入睡眠,当然也就避免了无效唤醒问题。

Linux内核的例子

Linux操作系统中,内核的稳定性至关重要,为了避免在Linux操作系统内核中出现无效唤醒问题,Linux内核在需要进程睡眠的时候应该使用类似如下的操作:

/* q 是我们希望睡眠的等待队列 */

DECLARE_WAITQUEUE(wait, current);

add_wait_queue(q, &wait);

set_current_state(TASK_INTERRUPTIBLE);

/* condition 是等待的条件 */

while (!condition) {

    schedule();

}

set_current_state(TASK_RUNNING);

remove_wait_queue(q, &wait);

面的操作,使得进程通过下面的一系列步骤安全地将自己加入到一个等待队列中进行睡眠:首先调用 DECLARE_WAITQUEUE() 创建一个等待队列的项,然后调用 add_wait_queue() 把自己加入到等待队列中,并且将进程的状态设置为 TASK_INTERRUPTIBLE 或者 TASK_INTERRUPTIBLE

然后循环检查条件是否为真:如果是的话就没有必要睡眠,如果条件不为真,就调用 schedule()。当进程检查的条件满足后,进程又将自己设置为 TASK_RUNNING 并调用 remove_wait_queue() 将自己移出等待队列。

从上面可以看到,Linux的内核代码维护者也是在进程检查条件之前就设置进程的状态为睡眠状态,然后才循环检查条件。如果在进程开始睡眠之前条件就已经达成了,那么循环会退出并用 set_current_state() 将自己的状态设置为就绪,这样同样保证了进程不会存在错误的进入睡眠的倾向,当然也就不会导致出现无效唤醒问题。

下面让我们用 Linux 内核中的实例来看看其是如何避免无效睡眠的,这段代码出自 Linux2.6 的内核 (/kernel/sched.c):

/* Wait for kthread_stop */

set_current_state(TASK_INTERRUPTIBLE);

while (!kthread_should_stop()) {

    schedule();

    set_current_state(TASK_INTERRUPTIBLE);

}

__set_current_state(TASK_RUNNING);

return 0;

上面的这些代码属于迁移服务线程 migration_thread,这个线程不断地检查 kthread_should_stop(),直到 kthread_should_stop() 返回 1 它才可以退出循环,也就是说只要 kthread_should_stop() 返回 0 该进程就会一直睡眠。

从代码中我们可以看出,检查 kthread_should_stop() 确实是在进程的状态被置为 TASK_INTERRUPTIBLE 后才开始执行的。因此,如果在条件检查之后但是在 schedule() 前有其他进程试图唤醒它,那么该进程的唤醒操作不会失效。

小结

通过上面的讨论,可以发现在 Linux 中避免进程的无效唤醒的关键是在进程检查条件之前就将进程的状态置为 TASK_INTERRUPTIBLE  TASK_UNINTERRUPTIBLE,并且如果检查的条件满足的话就应该将其状态重新设置为 TASK_RUNNING

这样无论进程等待的条件是否满足,进程都不会因为被移出就绪队列而错误地进入睡眠状态,从而避免了无效唤醒问题。

-End-

欢迎点赞收藏转发,感谢!!

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

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

相关文章

QGIS实现tiff影像栅格数据切片教程

实现栅格切片的方式有很多种,geoserver可以,qgis可以。自己写代码也可以,方式非常多。这篇文章介绍一下如何使用QGIS实现栅格切片。 首先我们要去下载一个qgis软件。下载地址如下: Welcome to the QGIS project! 随后我们打开软件,然后把要切片的栅格数据(tiff文件或者…

【Python 随练】逆序打印数字

题目: 给一个不多于 5 位的正整数,要求: 一、求它是几位数, 二、逆序打印出各位数字。 简介: 在本篇博客中,我们将解决一个关于正整数的问题。根据题目要求,我们需要判断给定的正整数是几位数…

你的仓库VS别人家的仓库,有什么不同?

导读: 近年来,随着新技术的蓬勃发展,企业的仓储管理水平大大提高,PDA、RFID、AGV、立体仓库、智能分拣线等各项新技术的应用层出不穷。然而,着眼于制造型企业,却发现仍有许多仓库处在比较原始、传统、粗放…

[译] DeepSpeed:所有人都能用的超大规模模型训练工具

我们于今年 2 月份发布了 DeepSpeed。这是一个开源深度学习训练优化库,其中包含的一个新的显存优化技术—— ZeRO(零冗余优化器),通过扩大规模,提升速度,控制成本,提升可用性,极大地…

如何绘制甘特图?这个软件来帮你

最好用的甘特图软件是什么?先看效果,再放教程: 这是大家用的比较多的excel做出的甘特图: 这是我做的动态甘特图: 两种方法,分享给大家,按需选择即可。 第一种:用Excel制作甘特图 用…

nx安装cuda与cudnn

本文参考 Jetson xavier nx 安装CUDA10.2,和cudnn8.0_jetson安装cuda_三羊木木的博客-CSDN博客 NX安装cuda和cudnn_nx cuda_viatea的博客-CSDN博客 进入nx系统后,输入 sudo apt-get update 更新源 目录 1 cuda 2 cudnn 1 cuda sudo apt-get ins…

如何注册GitHub账号

一、注册GitHub账号 1、浏览器URL中输入地址: https://github.com/ 2、跳转至GitHub官网 3、点击右上角sign up按钮 4、填写注册信息 这个大意是产品更新时消息会通过邮箱发给你,根据需求选择y or n 5、验证账户 有的小伙伴会出现错误信息 Unable to verify you…

考研算法28天:优化版插入排序(折半插入排序)【二分,插入排序】

算法介绍 算法介绍就是说我们原先写的插入排序的这段代码 for(int i1;i<n;i){//开始向前遍历&#xff0c;如果发现前面的元素比//x大的话&#xff0c;就将前面的元素放到x的后面int x q[i],ji;while( j && q[j-1]>x ){q[j] q[j-1];j--;}q[j] x;}我们里面那层…

Redis_6.2.12下载及安装(CentOS7)

文章目录 Redis安装-CentOS71、下载地址2、下载安装流程2.1 安装依赖2.2 官网下载tar包2.3 进入解压目录并编译2.4 启动Redis服务2.5 全局使用redis-cli、redis-server2.6 防火墙开启6379端口查看防火墙状态以及启动、关闭配置firewalld-cmd开启防火墙端口 3、设置开机自动启动…

SpringBoot集成百度人脸识别

SpringBoot集成百度人脸识别 1、概述2、账号申请账号登录注册创建应用 3、抽取模板工具AipFacePropertiesAipFaceTemplateapplication.yml 4、测试 ​ 人脸识别&#xff08;Face Recognition&#xff09;基于图像或视频中的人脸检测、分析和比对技术&#xff0c;提供对您已获授…

如何使用 SpringBoot 创建 RESTful API

如何使用 SpringBoot 创建 RESTful API SpringBoot是一个流行的Java框架&#xff0c;它可以帮助我们快速构建应用程序。其中&#xff0c;创建RESTful API是SpringBoot的常见用例之一。在本文中&#xff0c;我们将介绍如何使用SpringBoot创建RESTful API。 什么是RESTful API …

不同业务场景、不同数据类型,对应亚马逊云科技不同数据库服务

小小的改变&#xff0c;标志一个新时代的全面开启&#xff0c;一个数据库的云原生时代。前不久&#xff0c;Gartner公布了一组数据&#xff0c;引起了不小的讨论度。在2022年全球数据库管理系统的市场份额排名中&#xff0c;作为纯云厂商的亚马逊云科技&#xff0c;超越了老牌传…

电脑如何录屏?3个方法教您win10电脑怎么录屏!

案例&#xff1a;有时手机录屏满足不了我的录制需求&#xff0c;我需要在电脑上录屏&#xff0c;但我对电脑不是特别熟悉&#xff0c;不知道如何在电脑上录屏。 很多小伙伴想在电脑上录制视频&#xff0c;但又不知道如何操作。win10电脑录屏怎么录&#xff1f;有没有简单且详细…

趋势分享 | 多云时代数据安全面临的挑战

IT 和数据管理研究和咨询公司 EMA&#xff08;Enterprise Management Associates&#xff09;早前发布的一份《多云环境下的数据安全》&#xff08;Data Security in a Multi-Cloud World&#xff09;研究报告&#xff0c;调查了来自十个以上不同行业垂直领域、公司规模在 500 …

Kotlin对象和单例模式:探索这种强大设计模式的好处

Kotlin对象和单例模式&#xff1a;探索这种强大设计模式的好处 在Kotlin中&#xff0c;使用关键字"object"来定义一个单例对象。所谓单例对象&#xff0c;就是在整个应用程序中只有一个实例存在。简单来说&#xff0c;就好像只有一个蜘蛛侠一样&#xff0c;不可能同时…

群晖NAS遇到断电如何自动关机

群晖NAS遇到断电时如何自动关机 前置条件实现功能的想法具体实施的步骤开启群晖Linux的SSH登陆安装Putty或是Xshell远程登陆上群晖创建计划任务 前置条件 需要一台UPS&#xff08;品牌功能不限&#xff0c;只需续航5分钟以上就可以&#xff09; 实现功能的想法 通过监控网卡…

基于Python+Django+mysql+html图书管理系统

基于PythonDjangomysqlhtml图书管理系统 一、系统介绍二、功能展示1.用户登陆2.用户注册3.密码修改4.图书查询5.图书添加6.图书修改7.图书删除 三、其它系统四、获取源码 一、系统介绍 该系统实现了 用户登陆、用户注册、密码修改、图书查询、图书添加&#xff0c;图书修改、图…

一场场网络时代的“墨攻”

编辑&#xff1a;阿冒 设计&#xff1a;沐由 公输般为楚造云梯之械成&#xff0c;将以攻宋。子墨子闻之&#xff0c;起于鲁&#xff0c;行十日十夜&#xff0c;而至于郢&#xff0c;见公输般。 子墨子解带为城&#xff0c;以牒为械&#xff0c;公输般九设攻城之机变&#xff0c…

Apifox 自动生成接口

不太好用&#xff0c;很卡 1 下载Apifox https://apifox.com/?utm_sourcebaidu_pinzhuan&utm_mediumsem&utm_campaignpinzhuan&utm_contentpinzhuan&utm_termapifox 2 配置插件 3 IDEA安装插件 安装Apifox Helper 4 配置Apifox Helper ① Apifox 服务器地…

面试了数十家公司总结的Linux运维试题精华

下面是一名资深Linux运维求职数十家公司总结的Linux运维面试精华&#xff0c;助力大家跳槽找个高薪好工作。 1、什么是运维&#xff1f;什么是游戏运维&#xff1f; 1&#xff09;运维是指大型组织已经建立好的网络软硬件的维护&#xff0c;就是要保证业务的上线与运作的正常…