一文解析Linux进程的睡眠和唤醒

news2024/11/25 16:37:25

Linux进程的睡眠和唤醒

在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 和B,A 进程正在处理一个链表,它需要检查这个链表是否为空,如果不空就对链表里面的数据进行一些操作,同时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 进程也还是睡眠了。

 资料直通车:Linux内核源码技术学习路线+视频教程内核源码

学习直通车:Linux内核源码内存调优文件系统进程管理设备驱动/网络协议栈

避免无效唤醒

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

我们发现无效唤醒主要发生在检查条件之后和进程状态被设置为睡眠状态之前,本来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

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

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

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

相关文章

燃气巡检二维码

对燃气公司的输气管道和阀井等设施的巡检工作的管理目标是能降低成本、提高工作效率以及管理水平。但用纸质记录的方式进行燃气设备巡检有以下缺点: 1、难保证巡检真实性 无法客观、方便地掌握巡检人员巡检的到位情况,因而无法有效地保证巡检工作人员按计…

软件兼容性测试如何进行?怎么选择靠谱的软件检测公司?

软件兼容性测试是一项非常重要的工作,能够确保在不同的操作系统、设备、浏览器以及其他软件环境下,软件应用都能够正常运行。 一、软件兼容性测试如何进行? 确定测试的环境,包括操作系统、设备、浏览器等,并建立测试用例和测试…

Maven必要知识

参考笔记: https://www.wolai.com/arAiYJYCr6Kkfi2kZ8HxE8 1. Maven 概述 1.1 什么是 Maven Maven 是 Apache 软件基金会组织维护的一款专门为 Java 项目提供构建和依赖管理支持的工具。 Maven 作为依赖管理工具 jar 包的管理jar 包的来源jar 包之间的依赖关系…

使用R语言绘制折线图

R语言绘制折线图 一、绘制折线图1.载入bruceR(ggplot2)2.设置当前工作目录3.载入数据集4.查看数据结构5.绘制基础图形6.图形优化 二、绘制多重折线图1.载入数据2.绘制图形 一、绘制折线图 1.载入bruceR(ggplot2) (要…

【HAL库】STM32CubeMX开发----非阻塞延时实验----SysTick(滴答定时器)中断

STM32CubeMX 下载和安装 详细教程 【HAL库】STM32CubeMX开发----STM32F103/F207/F407----目录 前言 HAL库 有自带的 ms级 延时函数: HAL_Delay(); 缺点: 这是阻塞延时方式,就是延时期间,什么都不能干,这样很浪费资源。…

便携式车用CAN分析仪

产品简介 USBCAN-C系列便携式车用CAN分析仪,通过USB接口快速扩展一路CAN通道,使接入CAN网络非常容易,它具有一体式和小巧紧凑的外形,特别适合于随身携带。CAN接口采用金升阳电源模块和信号隔离芯片实现2500V DC电气隔离&#xff0…

能源设备智能维修AR远程作业指导平台降低运营成本

AR远程专家指导系统是一种基于AR增强现实技术搭建的远程协作解决方案,它可以让专家全视角掌握操作现场,并将专精知识和技能传递给远程现场的工作人员,以帮助他们解决各种技术难题和困难,赋能各行各业行业。 现场人员通过手机、平板…

OJ练习第104题——格雷编码

格雷编码 力扣链接:89. 格雷编码 题目描述 n 位格雷码序列 是一个由 2n 个整数组成的序列,其中: 每个整数都在范围 [0, 2n - 1] 内(含 0 和 2n - 1) 第一个整数是 0 一个整数在序列中出现 不超过一次 每对 相邻 整数…

数据流图(DFD)这么理解吗?

如何画好数据流程图(DFD)? 步骤: 确定系统的 input 和 output。由 表层 到 深层 画系统的顶层数据流图。自顶向下 逐层 分解,画出分层数据流图。 一、理解数据流图 基本概念 ~~ 数据流图 数据流(箭头&…

linux pinctrl 和 gpio 子系统 LED驱动

pinctrl 和 gpio 子系统 借助 pinctrl 和 gpio 子系统来简化 GPIO 驱动开发 pinctrl 子系统 pinctrl 子系统(drivers/pinctrl)的主要工作内容: ①、获取设备树中 pin 信息。 ②、根据获取到的 pin 信息来设置 pin 的复用功能 ③、根据获…

校招失败后,4面字节跳动软件测试工程师,竭尽全力....

下面是我面试字节跳动软件测试工程师的面试经验总结,希望能帮助到你们。 面试一 简单做一下自我介绍简要介绍一下项目/你负责的模块/选一个模块说一下你设计的用例get请求和post请求的区别如何判断前后端bug/3xx是什么意思说一下XXX项目中你做的接口测试/做了多少次…

免费润色文章的软件-自动修改文章润色的软件

免费润色文章的软件 免费润色文章的软件可以帮助用户快速地改善文本质量,进一步提高语言表达能力和流畅性,以下是其主要优势: 高效性:免费润色文章的软件能够快速进行润色处理,为用户节省时间和精力。相比手动润色的方…

瑞芯微RK3588核心板远程会诊等医学解决方案

RK3588处理器在医学领域的应用中,可以为远程会诊提供高性能和可靠的解决方案。以下是基于RK3588的远程会诊医学方面的解决方案: 远程高清图像传输: 利用RK3588处理器的高性能图像处理能力和高速网络接口,实现高清医学图像的实时传…

Dubbo 基于xml文件分析主流程源码 (4)

目录 前提 JDK实现SPI Dubbo实现SPI Dubbo源码 1. 找到Dubbo的命名空间处理类,也就是Dubbo的入口类 2. 将dubbo标签交给spring进行管理,就是从 BeanDefinition----> Bean的过程。 3. 服务暴露 4. 服务引入 总结 仿写Dubbo 前提 1. Dubbo源码…

CentOS7 yum update y更新后黑屏解决方案

解决方法 一 可以ssh访问 因为update的时候更新了系统内核,导致驱动问题,所以会黑屏。 更改一下yum的配置即可解决: vi /etc/yum.conf#增加:excludecentos-release*excludekernel*如果以上问题还未解决,可以试试下面的方法 其…

架构模式之分层模式

1 概念 分层架构模式是一种非常常见的架构设计模式,很多人都在用,可能不知道它的概念。分层模式背后的理念是,具有相同功能的组件将被组织成水平层。因此,每一层在应用程序中都扮演着特定的角色。 在这种模式中,…

自学自动化测试,第一份工作就18K,因为掌握了这些技术

我个人的情况是有1年自动化测试工作经验半年的实习经验,2020年毕业,专业通信工程,大一的时候学过C语言,所以一直对于编程感兴趣,之所以毕业后没做通信的工作,通信行业的朋友应该都明白,通信的天…

DolphinScheduler3.1.5安装部署

1.下载 DolphinScheduler下载地址:https://dolphinscheduler.apache.org/zh-cn/download/3.1.5 选择二进制包 下载,点击 jar 名称 就行 ​ 2.环境 CentOS Linux release 7.5.1804 (Core)java version "1.8.0_212"mysql version 5.7.16-log…

信息化 VS 数字化,哪个更适合当代企业?

各位数据的朋友,大家好,我是老周道数据,和你一起,用常人思维数据分析,通过数据讲故事。 现在大家都在谈数字化转型升级,那到底什么是数字化,是不是新瓶装旧酒呢?今天就和大家来谈谈…

【华为机考】专题突破 第二周:前缀和与差分 1109

刷题顺序参考于 《2023华为机考刷题指南:八周机考速通车》 前言 前缀和是指某序列的前n项和,可以把它理解为数学上的数列的前n项和,而差分可以看成前缀和的逆运算。合理的使用前缀和与差分,可以将某些复杂的问题简单化。 关于各类…