当前位置: 首页 > news >正文

【Linux】进程状态(二)

目录

前言:

一、进程状态:

1.运行状态(时间片)

2.阻塞状态

3.阻塞挂起状态

二、Linux进程状态:

1.运行状态(R)和阻塞状态(S) 

2.深度睡眠状态(D)

3.停止状态(T)

3.1使进程在后台运行 

4.追踪暂停状态(t)

5.死亡状态(X)和僵尸状态(Z)

5.1进程退出信息

三、孤儿进程:

四、命令总结:

总结:


前言:

我们已经知道了进程的一些属性,和如何创建子进程,那么接下来我们需要了解更多关于进程的概念。

一、进程状态:

我们主要讲述Linux的进程状态。

 我们先来了解什么是并行和并发。

并发是指多个任务或者事件在一段时间内交替执行。它强调的是在宏观上看起来这些任务是同时在进行,但在微观层面,实际上在某个瞬间可能只有一个任务在执行。还是用交通来类比,在一个单车道的道路上,有多辆车(任务)需要通过。由于只有一个车道,车辆不能同时通过,但是通过合理的调度,让每辆车都有机会前进,在一段时间内,看起来所有的车辆都在前进。例如,在一个单核处理器的计算机系统中,同时运行多个程序。由于只有一个核心,这些程序不能真正同时执行。操作系统会在这些程序之间快速切换,在一段时间内,每个程序都能得到执行时间,就好像它们在同时运行一样。

并行是指多个任务或者多个事件在同一时刻同时执行。这就好比有多个车道的高速公路,不同的车辆(任务)可以在不同的车道上同时前进,它们在物理时间上是重叠的。例如,在一个拥有多核处理器的计算机系统中,不同的核心可以同时处理不同的计算任务。假设我们有一个四核处理器,当执行四个独立的计算任务(如对四个不同的数据集进行数学运算)时,这四个任务可以同时在四个核心上运行,它们真正地在同一时间点都在执行,这就是并行处理。

Linux/Windows民用级别的操作系统,都是分时操作系统(调度追求公平)。

实时操作系统,实时操作系统一般要将一个特定的进程彻底执行完(在特定的领域会使用),不追求调度公平。

操作系统内会提供一个runqueue的运行队列:

而我们一般就是并发,也就是这样:

1.运行状态(时间片)

所以我们在理解运行状态时,就是当进程在运行队列中,该进程就处于运行状态。

此时CPU调度进程时,直接在runqueue队列中拿到一个进程的PCB即可。之后在CPU上运行完以后(也就是该进程的时间片到达以后),直接根据FIFO算法(先进先出)将时间片到达的进程再尾插到进程链表最后即可。

2.阻塞状态

操作系统有时如何管理硬件的呢?——先描述,在组织!

struct device
int type;
//比如1代表键盘
int status;//状态
//管理时间
//其他属性
}

struct device
{
    int type;
    //比如1代表键盘
    int status;//状态
    //管理时间
    //其他属性
}

此时如果有代码执行到了scanf时,CPU不可能一直让其在CPU上等待。其实struct device结构体中还有一个属性就是task struct* wait_queue:

所以当代码运行到scanf时,用户没有输入时,就会把该进程放入该设备的wait_queue中等待。当用户输入数据后,会再把该进程链入到runqueue中。

当进程在键盘的等待队列中,也被称为阻塞状态。所以进程都是在队列中的,状态只是看在那些队列中而已。

运行和阻塞的本质就是让不同的进程处在不同的队列中。等待的本质:链入目标外部设备,CPU不调度。

3.阻塞挂起状态

因为即使在等待队列中,也会占据内存,可此时内存已经严重不足了。此时操作系统为了自身的安全,会把该进程代码和数据换出到磁盘中,当然PCB还保留着。对于阻塞的进程,此时该用户又输入了内容,但是阻塞状态都是运行状态给定的,所以会直接把该进程再链入到运行状态的队列中,此过程为换入。

磁盘中有一个专门为此存在的分区——swap分区。当有很多这样处于阻塞的进程,操作系统内存严重不足时,可能会把所有处于阻塞状态的进程都换出到swap分区上。

以上是阻塞挂起状态。当然还有运行挂起,也就是正在运行时的进程加载到swap分区中。但是这个风险较大,一般操作系统不会开启。 

这个过程就会变慢,也就是时间换空间。swap分区一般设计为内存的等量大小,根据工程师分配。

一般云服务器swap功能会被禁用,系统一般对时间要求更高。如果此时操作系统内存快要占满,就会杀死正在运行的进程以保证自身安全,这也就是我们有时可能会遇到的闪退。

二、Linux进程状态:

我们之前说的都是进程的状态,但是接下来我们要说的是Linux进程的状态:

1.运行状态(R)和阻塞状态(S) 

我们写一个代码:

#include<stdio.h>

int main()
{
    int cnt = 0;
    while(1) 
    {
        printf("hello world, cnt: %d\n", cnt++);
    }
    return 0;
}

运行该进程,在另外一个窗口中查看该进程的运行状态:

此时可以看到我们有时能查到S状态,有时能查到R状态(后面的+是指的在前台下跑的)。这是为什么? 

其实S状态就是阻塞状态。你可能不相信,此时我们把代码更改一下:

#include<stdio.h>

int main()
{
    int cnt = 0;
    while(1) 
    {
        scanf("%d", &cnt);
        printf("hello world, cnt: %d\n", cnt);
    }
    return 0;
}

此时我们再修改代码,此时我们先把打印语句给注释掉再次观察状态:

此时为运行状态。 这是为什么?

一个进程的时间片是非常短的,而print是往显示器中写的,也就是IO,所以大部分时间都是阻塞状态,之后偶尔是R状态。

加入你使用的是云服务器,所以还要从你服务器所在地传入到你所在的地方,也就是网络IO,所以你可能会很难查到R状态。

在阻塞状态的进程,可以被kill发送信号给杀掉。这种可中断睡眠也称为浅睡眠。

2.深度睡眠状态(D)

阻塞等待状态的一种,不可中断睡眠,深度睡眠。

此时有10w条银行数据,需要往磁盘中写入,进程A会处于阻塞状态,知道在磁盘中写完这些数据。此时内存资源严重不足了,操作系统把这个进程A给干掉了。此时磁盘在写入第8w条数据时出现了错误,于是向进程A汇报错误,结果进程A已经挂掉了,于是磁盘就把这些数据清空了。

此时就出现了问题,因为这10w条数据很重要,于是操作系统就把向磁盘中写入数据进程状态的阻塞又分出了一种D状态,也就是不可终止的深度睡眠状态。

这里不做演示。当在公司中看到该状态,有两种可能:

  • 磁盘可能要挂掉了,磁盘空间不足或者磁盘老化了
  • 操作系统可能要挂掉了

3.停止状态(T)

我们再次修改code.c代码,让其死循环打印:

这里相当于发送暂停信号。 之后再对其发送-18SIGCONT继续信号:

可以看到刚才停止的程序又继续运行了。这时我们在程序运行的Xshell窗口上Ctrl+C发现无法终止程序,你可能也发现了,当我们查看code这个运行程序时,发现状态栏后面的+消失了,之前说过,这里的+代表在前台运行,此时已经转到后台运行了,而Ctrl+C只能杀死前台运行的程序,所以此时只能通过信号终止进程。

发送-9信号杀死code程序。

3.1使进程在后台运行 

我们当然也可以让进程在后台进行。先将code.c代码修改为每隔一秒打印一次:

在调用程序时在后面加上&即可让程序在后台运行, 也就是Windows上的最小化。但是此时还是在前台打印了,因为我们并没用和终端文件做脱离,如果脱离就看不到了。

T状态一般是进程做了非法但不致命的操作,被OS暂停了。

4.追踪暂停状态(t)

我们需要使用gdb来观察t状态,我们更改makefile,生成一个debug版本的可执行程序。

为了方便动态查询,我们编写一个简单的shell脚本,这里死循环每隔一秒打印关于code进程的信息。

while :; do ps ajx | head -1 && ps ajx | grep code | grep -v grep;sleep 1; done

此时运行代码,会在断点位置停下:

所以t(tracing stop追踪的暂停)状态的本质就是当前进程被暂停了。此时我们n执行一步程序。 

可以看到由t状态变为S+状态。 当进程被追踪的时候,断点停下,进程状态就是t。

5.死亡状态(X)和僵尸状态(Z)

dead状态和Z(zombie)状态。

我们首先要知道,进程为什么要被创建出来?进程创建出来是为了完成用户任务的。但是进程结束,任务到底完成没有?这是通过进程执行的结果告知父进程/操作系统的。

5.1进程退出信息

我们这里来了解一个命令:$?

代表上一个进程退出时的退出信息,一般0代表程序正常退出,也就是完成任务;而其他就是任务就是出错的。

我们平时写的.c文件,在结尾都会有return,我们修改code.c代码,让其正常执行完并使用echo $?来查看退出结果(更加具体的细节会在环境变量和进程替换中讲解)。

关于X(dead)和Z(zombie)状态,我们先举一个例子:

有一个大爷,跑的很快,忽然倒下了。你在旁边经过,作为新时代的三好少年,你不会袖手旁观,于是你拨打了110,之后警察封锁了现场,法医进行了鉴定,之后对外宣布大爷是如何死亡的。这里我们就可以把大爷理解为进程,其状态如下:

对于Linux为什么有Z状态,因为我们要维持退出信息,方便父进程和操作系统来进行查询。 

当一个进程退出的时候:

1.代码不会执行---首先可以立即释放进程对应的程序信息数据
2.进程退出,要有退出信息(进程的退出码)保存在tastk_struct(int exit_code)内部
3.管理结构task_struct必须被OS维护起来,方便用户未进行获取进程退出的信息。

所以一个进程在创建的时候第一步是先创建内核数据结构(struct_task),之后加载代码和结构。在销毁的时候先释放代码和结构,之后OS维护内核数据结构,最后根据情况释放

接下来就用代码证明,我们先创建子进程,此时父子同时存在,之后让子进程退出,父进程什么都不做。

#include<stdio.h>
#include<unistd.h>

int main()
{
    printf("父进程运行,pid: %d, ppid: %d\n\n",getpid(), getppid());

    pid_t id = fork();
    if (id == 0) 
    {
        //子进程
        int cnt = 5;
        while(cnt)
        {
            printf("我是子进程,我的pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt);
            sleep(2);
            cnt--;
        }
    }
    else 
    {
        //父进程
        while(1)
        {
            printf("我是父进程,我的pid: %d, ppid: %d\n", getpid(), getppid());
            sleep(1);
        }
    }
    return 0;
}

我们再次开启一个窗口,持续观察进程和进程状态,还是使用这个代码进行观察:

while :; do ps ajx | head -1 ; ps ajx | grep myprocess | grep -v grep; sleep 1; done

defunct : 失灵的,不再使用的,死的 

当然我们还可以使用另外一种方法观察僵尸状态,我们修改myprocess.c代码,将子进程代码修改为死循环:

我们再次新开一个窗口,发送信号将子进程杀掉:

僵尸状态的进程,也称僵尸进程,维护自己的task_struct,方便父进程读取状态。也就是说,没人管它,它就是一直僵尸,task_struct也就一直占用内存资源,即使对应的代码和数据已经释放。此时存在的问题就是内存泄漏。

我们以前一般写的程序存在内存泄露我们一般发不现问题。是因为我们很快就把程序执行完了,但是在一个大项目中,不会很快就执行完程序,所以我们要避免内存泄漏。

最后,我们要回收这个进程,需要父进程读取子进程信息,子进程才会自动退出,此时task_struct也就会释放。

此时我们已经将子进程杀掉了,再次发送信号是无法杀掉的,因为你无法杀掉一个在概念上已经死掉的进程。

如何回收?我们后面再讲,不过这里先抛砖引玉一下,wait方法回收(在2号系统调用手册中)。

X状态是瞬时状态,我们捕捉不到,但是确实存在。

三、孤儿进程:

我们已经知道了僵尸进程,也就是父在子退;而还有另一种情况,就是父退子在。我们还用刚才的代码进行演示,这次我们将父进程杀死。

我们可以看到子进程成为了后端进程,所以先发送信号将其杀死。

这就奇怪了,在之前我们把子进程杀掉,其会变成僵尸状态;而我们把父进程杀掉他却没有变成僵尸状态,这是为什么?因为父进程的父进程是bash,当我们杀死父进程时,bash会自动回收。而子进程没有了父亲,PID为1的进程会将其领养。

接下来我们看看PID为1的进程时谁。执行top命令,我们可以先将其理解为任务资源管理器。

因为操作体统必须对所有的进程进行管理,当一个子进程没有父亲时,系统进程就会将其领养。 

四、命令总结:

top命令:相当于任务资源管理器。

可执行程序 + &:该进程在后台运行。

echo $?:查看上次进程退出信息。

死循环查看进程信息脚本:

while :; do ps ajx | head -1 && ps ajx | grep code | grep -v grep;sleep 1; done

总结:

我们知道了进程的状态并且知道如何观察,但是这只是冰山一角,我们目前只是初窥门径,欲知后事如何,且听下回分解(记得追剧啊!)。

http://www.dtcms.com/a/40280.html

相关文章:

  • 有问题的两题
  • 2024大模型技术全景解构:从开源生态到商业落地的深度博弈
  • SpringWeb
  • 初识Skywalking
  • 基于MATLAB红外弱小目标检测MPCM算法复现
  • libGL.so.1: cannot open shared object file: No such file or directory-linux022
  • 数据结构绪论
  • 如何快速开放 Linux 系统中的任意端口?
  • Idea2024中搭建JavaFX开发环境并创建运行项目
  • 如何配置ssh免密登录
  • 根据经纬度获取时区并返回当前时间
  • MybatisPlus学习要点记录(更新中)
  • 在使用 npm link 进行本地 npm 包调试时,是否需要删除项目中已安装的依赖包取决于你的调试场景和依赖管理方式
  • leetcode209------长度最小的子数组、滑动窗口
  • TCP/IP 5层协议簇:数据链路层(交换机工作原理)
  • nacos升级2.3.0-2.4.1
  • 【MySql】EXPLAIN执行计划全解析:15个字段深度解读与调优指南
  • 【深度学习】PyTorch v2.6 Overview
  • 设计后端返回给前端的返回体
  • 钉钉MAKE AI生态大会思考
  • C++ | 高级教程 | 泛型模板
  • 数据结构秘籍(一)线性数据结构
  • 注意力机制有哪些,原理是什么
  • 细说STM32F407单片机RS485收发通信实例及调试方法
  • wordpress使用CorePress主题设置项总结
  • Elasticsearch:使用经过训练的 ML 模型理解稀疏向量嵌入
  • Python Pandas带多组参数和标签的Oracle数据库批量数据导出程序
  • MySQL-MATCH ... AGAINST工具
  • linux--多进程开发(5)--进程间通信(IPC)、linux间通信的方式、管道
  • 全价值链数字化转型:以美的集团为例,探索开源AI大模型与S2B2C商城小程序源码的融合应用