Linux--进程(2)

目录

前言 

1. 进程的状态

1.1  进程排队

1.2 运行,阻塞,挂起 

2.Linux下具体的进程状态 

2.1僵尸和孤儿

3.进程的优先级 

 4.Linux的调度与切换


前言 

这篇继续来学习进程的其它知识

上篇文章:Linux--进程(1)-CSDN博客


1. 进程的状态

关于进程的状态:

首先我们来轻量聊一下进程排队这件事情--队列

然后谈一谈教材上关于进程状态的表述---运行,阻塞,挂起

最后看看Linux下具体的进程状态(具体的看看什么是运行,什么是阻塞,什么是挂起)


1.1  进程排队

进程不是一直在运行的;进程放在了cpu上,也不是一直在前全运行的

进程不是在一直运行的,也可能在等待某中软硬件资源,比如我们在写c语言代码的时候,运行到scanf那一行,进程就在等待输入。

进程放在了cpu上,也不是一直在前全运行的:每个进程都会被分配一个时间段,我们称之为时间片,如果时间结束该进程还在运行,CPU将进行剥夺资源并分配给另一个进程。我们写代码的时候有时候会遇到死循环,当代码运行生成了进程,进程时间结束,我们会发现循环仍未终止,但实际上这个进程资源已经被剥夺了。在Linux系统上,他可能在5毫秒到800毫秒之间。由于时间片极短,用户不会感觉到程序的切换,从而形成了多个进程同时运行的错觉。

我们接着来谈谈排队的问题

首先进程排队一定是在等待某种资源

1.进程=task_struct+可执行程序,进程排队不是可执行程序在排队,而是task_struct对象(PCB)在排队,比如你实习投简历的时候,是简历在排队不是你人在排队。

2.PCB的问题

一个进程的PCB可以被链入多种数据结构中,在不同的场景下,操作系统可能会采用不同的数据结构来组织和管理PCB。一个PCB通常不会同时被链接到多种数据结构中。它会被放置在最适合当前管理需求的数据结构中。例如,当进程处于  就绪状态时,它的PCB会被放在就绪队列中;当进程因等待某个事件而被阻塞时,它的PCB会被移到阻塞队列中。操作系统会根据需要动态地更新和移动PCB,以确保进程能够按照正确的顺序和方式得到调度和执行。


1.2 运行,阻塞,挂起 

1.所谓的状态本质就是在task_struct中的一个整型变量

eg:所谓的状态不过是status所被给予的数字

2.状态决定了什么?

 状态决定了进程的后续动作,Linux中可能存在多个进程都要根据他的状态执行后续的动作。在这种情况下就需要让进程去排队了,一个CPU一个运行队列

运行状态:运行状态是进程实际占用CPU执行其程序代码的状态。我们在主流的操作系统中,只要进程处在运行队列中排队,我们都可以称这个进程在运行状态。


在前面我们说了,进程不是在一直运行的,也可能在等待某中软硬件资源,进程放在了cpu上,也不是一直在前全运行的。

在这里我以硬件为例,来了解阻塞状态。

操作系统,管理硬件,要先把硬件表述起来,再去管理,每个硬件都有属于自己的对象,cup也是硬件,他有他的运行队列,其它硬件也有其它硬件的队列,称之为等待队列。

我引用上面的例子,当代码运行到scanf的时候,进程就在等待输入,这时进程的状态就会从运行状态变为阻塞状态,进程会被操作系统(OS)搬迁到键盘的等待队列中去,当输入完成之后,OS就会知道进程已经就绪了,这时进程又会重新被搬迁到运行队列中去,此时进程变为运行状态。(状态的变迁,实际上就是OS将PCB搬迁到不同的队列中)

总结阻塞状态:当我们的进程正在等待软硬件资源的时候,资源如果没有就绪,我们的进程task_struct只能:1.将自己设置为阻塞状态 2.将自己的PCB链入等待的资源提供的等待队列中去。


挂起状态:这个状态并不常见,这个状态的前提是计算机资源已经比较吃紧了。因为这些进程暂时没有动作,此时OS就会将阻塞状态的进程(代码和数据,不包括PCB)转到外设中存(磁盘的swap分区)储起来(唤出),需要时再唤入。


2.Linux下具体的进程状态 

为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在Linux内核里,进程有时候也叫做任务)。
下面的状态在kernel源代码里定义:
 

/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
  • R运行状态(running) : 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
  • S睡眠(阻塞)状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。

 我们举一个样例,来看看在Linux中状态具体的样子。

我们运行这个代码,查看了这个进程的状态发现它处于S(阻塞)状态,这时我们就有疑问了,这段代码进入了死循环应该是一直在运行的啊,不应该是R(运行)状态吗?

这是因为,printf的缘故,这个程序的大多数时间都在执行printf函数,这时就会发生系统调用访问外设,在这些系统调用期间,进程大多数时间都是处于等待的状态。所以就会处于S状态。

此时我们把printf删去,这个程序就只做死循环了,不会去访问任何外设,这时候进程就一直处于运行状态了。

进程后面+的含义:表示这个进程是前台进程。如果我们在运行可执行程序的时候后面加上&符合,这时就会变成后台进程,没有+进行标识了。此时进程进行CTRL+C是无法终止的,只能使用kill指令杀掉该进程。

  • D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。(在资源极度紧张的情况,操作系统主动杀掉了进程,为了避免这种情况,就引入了这个状态,等磁盘工作完返回给进程,进程任务完成了才结束。),这个状态也属于阻塞状态,因为它在等待资源就绪。
  • T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
  • t停止状态,这个状态一般在调试代码,对代码进行打断点的操作时,等待你的的下一步操作,此时进程就会处于t状态。(T/t也可以理解成一种阻塞状态,因为在这个状态进程也是在等待某种资源的就绪)

kill命令的第19号选项,就可以让进程暂停,在输入18号选项就可以就绪了。但被继续的进程会自动转为后台进程。下面是示例:

  • X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态


2.1僵尸和孤儿

  •  Z僵尸状态(zombie)

1.僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
2.僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
3.所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
来一个创建僵死进程例子:
 

#include <stdio.h>
#include<stdlib.h>
#include <unistd.h>
int main()
{pid_t id = fork();if (id == 0){//childint cnt = 5;while (cnt){printf("I am child,pid:%d,ppid:%d\n", getpid(), getppid());sleep(1);cnt--;}exit(0);//让子进程退出                                           }//fatherwhile (1){printf("I am father,pid:%d,ppid:%d\n", getpid(), getppid());sleep(1);}return 0;
}

编译并在另一个终端下启动监控

开始测试

看到结果

我们看到子进程已经进入僵尸状态了,此时子进程已经死了,只是还要维持到被父进程读取

为什么要有Z状态?

创建进程是希望这个进程给用户完成工作的,子进程必须有结果数据,PCB中的。

僵尸进程危害

  1. 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!
  2. 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说, Z状态一直不退出, PCB一直都要维护?是的!
  3. 那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构
  4. 对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
  5. 内存泄漏
  6. 如何解决呢?在这里我们见一个函数wait,后面我们在详细的介绍

开始5s子进程和父进程一起进行,5s后子进程进入僵尸状态父进程,10s父进程结束执行wait函数(回收子进程资源,获取子进程状态)(bash创建的子进程bash会自动回收)


孤儿进程
  1. 父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
  2. 父进程先退出,子进程就称之为“孤儿进程”

我们重新设置一下代码,让父进程先结束:

测试结果:

我们发现父进程结束后,子进程转为了后台进程,且父进程为”1“,1号进程其实就是操作系统,也就是说父进程结束后,它的子进程将会被操作系统领养。


 


3.进程的优先级 

是什么?

前提:进程需要访问某种资源,进程通过一定的方式(排队),确认享受资源的先后顺序

为什么?

 因为资源有限。

怎么办?

在Linux中,你可以使用多种方法来查看和修改进程的优先级。

在linux或者unix系统中,用ps –l命令则会类似输出以下几个内容:

我们很容易注意到其中的几个重要信息,有下:

  • UID : 代表执行者的身份
  • PID : 代表这个进程的代号
  • PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
  • PRI :代表这个进程可被执行的优先级,其值越小越早被执行
  • NI :代表这个进程的nice值

PRI and NI

  • PRI也还是比较好理解的,即进程的优先级(Linux的默认优先级是80),或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高(Linux优先级的范围是:[60,99]--->40。
  • 那NI呢?就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值
  • PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为: PRI(new)=PRI(old)+nice
  • 这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行。
  • 所以,调整进程优先级,在Linux下,就是调整进程nice值。
  • nice其取值范围是-20至19,一共40个级别。

PRI vs NI

  • 需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。
  • 可以理解nice值是进程优先级的修正修正数据

用top命令更改已存在进程的nice:进入top后按“r”–>输入进程PID–>输入nice值
eg:

初始状态

 我给nice的值为10

此时PRI的值就为90了

Linux调整优先级为什么是要受限制的

如果不加限制,将自己进程的优先级调整的非常高,别人的优先级调整的非常低。优先级高的进程,优先得到资源--后续还有源源不断的进程产生,导致常规进程很难享受到CPU的资源!这就产生了进程饥饿问题。


 4.Linux的调度与切换

概念准备:进程在运行的时候,放在CPU上,直接必须把进程代码跑完,才行吗?

不对,现代操作系统,都是基于时间片进行轮转执行的。

  • 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
  • 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
  • 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
  • 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发

对于进程切换的理解:

        进程切换,就像我们在日常生活中更换任务或活动一样,是操作系统中非常关键的一个环节。当我们从一个进程切换到另一个进程时,操作系统需要确保前一个进程的状态被保存下来,同时加载并恢复新进程的状态。

        具体来说,进程切换涉及几个关键步骤。首先,系统会暂停当前正在运行的进程,保存其上下文信息,这包括CPU寄存器的内容、内存管理信息以及程序计数器等。这些信息对于进程的恢复和继续执行至关重要。(进程在执行的时候,它的数据是保存在CPU的寄存器中的。)

        接下来,系统会选择一个新的进程来运行。这个选择过程可能基于多种因素,如进程的优先级、系统的调度策略等。一旦选择了新的进程,系统就会恢复其上下文信息(进程在运行的过程中,要产生大量的临时数据,放在cpu的寄存器中!cpu内部所有的临时数据,我们叫做进程的硬件上下文),将其状态设置为就绪状态,并将其加载到CPU中。

        完成这些步骤后,新的进程就可以开始执行了。而原来的进程则处于暂停状态,等待下一次被调度执行。
        进程切换的开销是相对较大的,因为涉及保存和恢复上下文信息、更新系统数据结构等操作。因此,操作系统会尽量优化进程切换的过程,减少不必要的开销,以提高系统的整体性能。(执行 保存 恢复 再执行)

注意:cpu内的寄存器只有一套,寄存器内部保存的数据,可以有多套!虽然寄存器数据放在了一个共享的cpu设备里面,但所有的数据,其实都是被进程私有的!!!

cup内所有的数据在任意一个时刻只属于一个进程,怎么理解?

        在任意一个时刻,CPU内的数据都属于当前正在执行的进程。这是操作系统通过进程调度和CPU管理实现的,确保了每个进程都能独占地使用CPU资源,并且其数据不会与其他进程的数据混淆或冲突。
        需要注意的是,虽然CPU内的数据在任意时刻只属于一个进程,但在多核CPU系统中,每个核心可以同时执行不同的进程。这种情况下,每个核心的数据仍然只属于它当前执行的进程,但不同的核心可以同时处理不同进程的数据。
(寄存器!=寄存器的内容)


对于调度的理解:

这是一个cpu的运行队列(Linux实现进程调度的算法,考虑优先级,考虑饥饿,考虑效率)

蓝色区域(表示活跃队列):时间片还没有结束的所有进程都按照优先级放在该队列

queue[140]:它是task_struc*类型,task_struct* queue[140] 是一个包含140个元素的数组,每个元素都是一个指向 task_struct 的指针,每个指针指向一个进程。它分为0-99,和100-139连个部分。

        如果系统使用优先级调度,那么0-99这部分的队列可能用于存储优先级较高的进程,为了保证用户的公平性,这个区段的进程优先级都差不多。

        100-139这部分包含数组的剩余40个元素。这些元素可能用于存储另一种类型的进程或任务(优先级较低的进程,处于不同状态的进程:这些进程可能处于阻塞状态,等待某些资源或事件。后台任务:这些可能是系统后台运行的任务,不需要立即执行)

        为什么这里的范围刚好是40?因为进程的优先级刚好有40个等级,cup调用优先级只需要根据优先级依次去调用就好了。

bitmap[5]:(bitmap)常用于快速检查一个元素是否存在于某个集合中,或者用于高效地表示和管理大量的二进制数据。具体到bitmap[5],他是int类型的,那么他就有160个bit位,可以管理160个位置的信息。

        如果bitmap[5]的某个位被设置为1,这可意味着该位置的资源已经被占用;如果设置为0,则表示该资源是空闲的。队列调度算法在决定下一个要执行的任务时,可能会查看这个bitmap来确定哪些资源是可用的,从而选择能够利用这些资源的任务。

        bitmap[5]本身并不执行调度算法,它只是调度算法在决策过程中可能参考的一个数据点。实际的调度逻辑会更为复杂,可能涉及多个因素,如任务的优先级、资源的需求和可用性、系统的当前状态等。

红色区域(表示过期队列):这个结构和蓝色区域的一样,他也是一个优先级数组

过期队列和活跃列结构一模一样。
过期队列上放置的进程,都是时间片耗尽的进程。
当活动队列上的进程都被处理完毕之后,对过期队列的进程进行时间片重新计算。


下面是对上面结构的理解,有一个q的结构体,我们封装成结构体数组,array[0]表示蓝色区域,array[1]表示红色区域。当活跃队列的进程只需完毕后,交换两个结构体指针的指向就好了,让过期队列的进程得以执行,这样就以O(1)的时间复杂度,实现了对进程的调度

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

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

相关文章

ES12 学习

文章目录 1. 逻辑赋值操作符2. 数字分隔符_3. replaceAll4. Promise.any5. WeakRefs6. FinalizationRegistry 1. 逻辑赋值操作符 逻辑赋值操作符 ??、&&、|| let a true let b true a && b // a a && b a || b // a a || b obj.introduction ?…

Redis -- 缓存击穿问题

缓存击穿问题也叫热点Key问题&#xff0c;就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了&#xff0c;无数的请求访问会在瞬间给数据库带来巨大的冲击。 常见的解决方案有两种&#xff1a; 互斥锁 逻辑过期 逻辑分析&#xff1a;假设线程1在查询缓存之后&…

静态路由协议实验1

要求&#xff1a; 使用静态路由协议使得全网可达。 第一步、规划IP地址。并配置IP。 第二步、写静态路由 [r1]ip route-static 192.168.3.0 24 192.168.2.2 [r1]ip route-static 192.168.4.0 24 192.168.2.2 [r1]ip route-static 192.168.5.0 24 192.168.2.2[r2]ip route-st…

一、next-auth 身份验证凭据-使用电子邮件和密码注册登录

一、next-auth 身份验证凭据-使用电子邮件和密码注册登录 文章目录 一、next-auth 身份验证凭据-使用电子邮件和密码注册登录一、前言二、前置准备1、环境配置2、相关库安装&#xff08;1&#xff09;vercel 配置&#xff08;2&#xff09;Yarn 包管理配置 3、next项目初始化与…

【Oracle】oracle、mysql、sql server三者区别

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是《Oracle》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对知识…

Shell脚本之基础-2

目录 一、字符处理 cut命令 awk命令 sed命令 字符串排序 二、条件判断 文件类型判断 文件权限判断 两个文件的判断 整数比较 字符串判断 多重判断 三、流程控制 if分支 if else 双分支结构 case分支 for循环 while循环 一、字符处理 cut命令 命令格式&#x…

深入剖析JavaScript中的this(下)

五、事件处理函数的this 5.1 事件绑定 <button id"btn">点击我</button>function handleClick(e) {console.log(this);// <button id"btn">点击我</button> }document.getElementById(btn).addEventListener(click, handleClick…

CSS基础:4种简单选择器的详解

你好&#xff0c;我是云桃桃。 一个希望帮助更多朋友快速入门 WEB 前端的程序媛。大专生&#xff0c;2年时间从1800到月入过万&#xff0c;工作5年买房。 分享成长心得。 261篇原创内容-公众号 后台回复“前端工具”可获取开发工具&#xff0c;持续更新中 后台回复“前端基础…

MySQL客户端安装并配置免密登录

最近在写脚本时需要向MySQL数据库中存储数据&#xff0c;且脚本运行的服务器与MySQL服务器不是同一台服务器&#xff0c;而且需要保证MySQL密码的安全性&#xff0c;不能在脚本中暴露&#xff0c;所以就需要在服务器上安装MySQL客户端&#xff0c;并配置免密登录。 一、虚拟机…

Git安装教程(图文安装)

Git Bash是git(版本管理器)中提供的一个命令行工具&#xff0c;外观类似于Windows系统内置的cmd命令行工具。 可以将Git Bash看作是一个终端模拟器&#xff0c;它提供了类似于Linux和Unix系统下Bash Shell环境的功能。通过Git Bash&#xff0c;用户可以在Windows系统中运行基于…

Vue项目登录页实现获取短信验证码的功能

之前我们写过不需要调后端接口就获取验证码的方法,具体看《无需后端接口,用原生js轻松实现验证码》这个文章。现在我们管理后台有个需求,就是登录页面需要获取验证码,用户可以输入验证码后进行登录。效果如下,当我点击获取验证码后能获取短信验证码: 这里在用户点击获取…

Linux 线程:线程同步、生产者消费者模型

目录 一、死锁 二、条件变量实现线程同步 1、为什么需要线程同步 2、条件变量、同步、竞态条件 3、条件变量函数&#xff1a;初始化 销毁 等待 唤醒 4、实现简单的多线程程序 不唤醒则一直等待 实现线程同步 三、生产者消费者 1、借助超市模型理解 2、优点 四、基于…

数字乡村创新实践探索:科技赋能农业现代化与乡村治理体系现代化同步推进

随着信息技术的飞速发展&#xff0c;数字乡村作为乡村振兴的重要战略方向&#xff0c;正日益成为推动农业现代化和乡村治理体系现代化的关键力量。科技赋能下的数字乡村&#xff0c;不仅提高了农业生产的效率和品质&#xff0c;也为乡村治理带来了新的机遇和挑战。本文旨在探讨…

Linux 环境下 Redis基础配置及开机自启

Linux 环境下 Redis基础配置及开机自启 linux环境安装redis<redis-6.0.5.tar.gz> 1-redis基本安装配置 解压 获取到tar包后&#xff0c;解压到相关目录&#xff0c;一般是将redis目录放在usr/local/redis目录下&#xff0c;可以使用-C指定到解压下目录 tar -zvxf re…

Java数据结构栈

栈&#xff08;Stack&#xff09; 概念 栈是一种先进后出的数据结构。 栈的使用 import java.util.Stack; public class Test {public static void main(String[] args) {Stack<Integer> s new Stack();s.push(1);s.push(2);s.push(3);s.push(4);System.out.println(s…

3. python练习题3-自由落体

3. python练习题3-自由落体 【目录】 文章目录 3. python练习题3-自由落体1. 目标任务2. 解题思路3. 知识回顾-%占位符格式化处理3.1 概述3.2 占位符的多种用法3.3 格式化操作符辅助指令3.4 将整数和浮点数格式化为字符串 4. 解题思路4.1 球第1次下落4.2 球第2次下落 5. 最终代…

day60 动态规划part17

这两题看了自己写的笔记还不懂的话&#xff0c;看看这个up的思路就行&#xff1a; https://space.bilibili.com/111062940/search/video?keyword%E5%9B%9E%E6%96%87 647. 回文子串 中等 提示 给你一个字符串 s &#xff0c;请你统计并返回这个字符串中 回文子串 的数目。 回…

学习【RabbitMQ入门】这一篇就够了

目录 1. RabbitMQ入门1-1. 同步调用1-2. 异步调用1-3. MQ技术选型1-4. RabbitMQ介绍消息模式 1-5. SpringAMQPBasic QueueWork QueueFanout ExchangeDirect ExchangeTopic Exchange消息转换器 1. RabbitMQ入门 1-1. 同步调用 优势&#xff1a; 时效性强&#xff0c;等待到结…

Windows 2008虚拟机安装、安装VM Tools、快照和链接克隆、添加硬盘修改格式为GPT

一、安装vmware workstation软件 VMware workstation的安装介质&#xff0c;获取路径&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1AUAw_--yjZAUPbsR7StOJQ 提取码&#xff1a;umz1 所在目录&#xff1a;\vmware\VMware workstation 15.1.0 1.找到百度网盘中vmwa…

Mysql的基本命令

1 服务相关命令 命令描述systemctl status mysql查看MySQL服务的状态systemctl stop mysql停止MySQL服务systemctl start mysql启动MySQL服务systemctl restart mysql重启MySQL服务ps -ef | grep mysql查看mysql的进程mysql -uroot -hlocalhost -p123456登录MySQLhelp显示MySQ…