进程状态|操作系统|什么是pcb|什么是僵尸进程 |什么是孤儿进程 【超详细的图文解释】【Linux OS】


说在前面

今天给大家带来操作系统中进程状态的详解。

本篇博主将通过从进程状态的广泛概念,深入到Linux操作系统详细的一些进程状态。在解释进程状态的过程中,博主还会穿插一些操作系统一些重要概念!本篇干货满满,请大家不要吝啬一键三连哦!

前言

那么这里博主先安利一下一些干货满满的专栏啦!

手撕数据结构https://blog.csdn.net/yu_cblog/category_11490888.html?spm=1001.2014.3001.5482这里包含了博主很多的数据结构学习上的总结,每一篇都是超级用心编写的,有兴趣的伙伴们都支持一下吧!
算法专栏https://blog.csdn.net/yu_cblog/category_11464817.html这里是STL源码剖析专栏,这个专栏将会持续更新STL各种容器的模拟实现。

STL源码剖析https://blog.csdn.net/yu_cblog/category_11983210.html?spm=1001.2014.3001.5482


什么是进程的pcb结构体

首先,我们先要复习一下操作系统概念中的一句很重要的话:先描述,再组织

这其实就是pcb结构体存在的意义

进程的描述

在OS中,对于每一个进程,操作系统都会维护一个pcb结构体来管理每一个进程,每个pcb结构体里面存了有关该进程的信息。

而在Linux操作系统中,pcb结构体成为task_struct结构体

task_ struct内容分类:

标示符: 描述本进程的唯一标示符,用来区别其他进程。

状态: 任务状态,退出代码,退出信号等。

优先级: 相对于其他进程的优先级。

程序计数器: 程序中即将被执行的下一条指令的地址。

内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针 

上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。 

I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。

记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。

其他信息

进程的组织

可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct链表的形式存在内核里。 查看进程。

广泛意义上的进程状态

广泛意义上的进程状态,其实就是我们在高校计算机科学与技术专业中所修的《操作系统原理》课程中对进程的解释。

这里大家要注意:这种解释是非常广泛的,也就是它可以针对于任何一种操作系统进行解释,都是正确的。但是我们在学习过程中,我们需要深入学习一种操作系统的进程状态,而不是广泛的学习,因此,下一节博主会详细介绍Linux操作系统的进程状态。

此时,我们随便在网上找一张图,其实大同小异,如图所示是博主在写博客时随便找的一张图。

在这里,博主会抓住几个重要的状态进行详细讲解,其余的状态,博主只需要稍微提一下,大家就能够明白了。

新建、运行、阻塞、挂起、死亡

新建

这个状态就是字面意思,进程刚被创建的时候的状态。

概念补充:

这里博主要给大家补充一个概念。在我们的操作系统中,cpu会维护一个叫做运行队列的数据结构。

该数据结构上链接的就是我们进程的pcb结构体。cpu会根据一定的调度算法(调度器决定),对运行队列中的pcb进行运算,即对里面的进程进行运算。(注意:cpu会根据调度算法决定进程的运行顺序,而不是像普通队列一样先进先出)

其中,新建状态指的是,进程的pcb刚被创建,但是还没有进入运行队列时候的状态。(其实在现实操作系统中,并不会存在这种状态,因为pcb结构体一被创建,一般就会直接进入cpu的运行队列中了)

运行

一个进程处于运行态:该进程的pcb在cpu的运行队列中排队或被cpu计算时的状态。

这里要纠正一个常见的误区,运行态不仅仅代表该进程的代码正在被运行,在排队也算运行态。

对于在cpu的运行队列中排队,我们给一个专业的描述:等待cpu资源就绪

阻塞

进程正在等待非cpu资源就绪。(非cpu资源:网卡、磁盘、键盘等)

挂起

如图所示

当内存不足的时候,OS通过适当的置换进程代码和数据到磁盘上时,该进程的状态 

 其他的状态博主在详细介绍Linux操作系统的进程状态时给大家解释。

Linux操作系统的进程状态

在介绍状态之前,博主先给大家准备好要用的代码文件

先把Makefile和myproc.c准备好

在大家对Linux的命令行和vim的相关操作十分熟悉后,博主认为大家可以用vscode连接云服务器了,在此之前,博主还是建议大家多操作命令行。

后面我们的代码就在myproc.c中操作,然后make即可。 

博主再提供一条打印系统上正在运行进程的脚本命令:

while :; do ps axj | head -1 && ps axj | grep myproc | grep -v grep; sleep 1; done

其中ps axj打印进程,head -1 打印表头,ps axj | grep myproc 打印一个叫myproc的进程,grep - v grep表示不打印grep这个进程(因为我们调用grep搜索,grep这个进程也在跑,Linux中每一条命令都是一个可执行程序,这个大家应该都很熟悉了)while括起来一秒打印一次。    

R状态

代码如下:

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

int main()
{
    while(1)
    {
        //printf("I am a process!\n");
    }
    return 0;
}

  我们make一下,用脚本打印一下进程,如图所示

 R状态就是运行态,而后面的‘+’代表该进程是一个前台进程。

前台进程

该进程在前台运行,即当我们开启一个命令行(bash)运行该进程,该进程在运行结束之前会占有当前bash,命令行的表现就是:卡住了。

前台进程我们可以通过ctrl C终止,如上图所示。

展示一个R状态的后台进程

那么我们要如何终止该进程呢?

其中一种方式是通过kill命令,并带上9号信号去终止。(现在我们只需要知道kill -9怎么用即可,细节我们不展开)

 讲到这里,我们运行态就讲完了。

从现在开始,博主将不对进程状态后面的+做解释,+就是前台进程的意思,仅此而已。

S状态

对应于我们广泛意义上的阻塞状态(睡眠态)

意味着进程正在事件完成(等待某种非cpu资源)

代码如下 :

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

int main()
{
    while(1)
    {
        printf("I am a process!\n");
    }
    return 0;
}

此时我们肯定会抛出一个疑问:这个代码和R状态的验证代码是一样的,为什么这个会是S?

大家先别急,我们先验证一下 。

此时我们发现,状态中,即有R也有S,这是为什么。

我们大家都知道,我们打印一句话,其实对于操作系统来说,就是一次IO操作。

IO操作的速度是非常慢了,其速度远不及cpu的运算速度。因此一开始我们打印语句,显示器可以跟上cpu的脚步,我们打印的就是R。后面逐渐显示器跟不上了,此时,进程正在等待显示器资源的就绪!因此就是S状态!

此时的S状态是可中断睡眠

可中断睡眠,即我们可以给这个进程发信号!

如果我们ctrl C,会把这个进程杀掉。

如果我们发送kill -9 信号,会把这个进程杀掉。

如果我们发送kill -19 信号,会把这个进程暂停。

在这里我们先不讨论暂停是什么,我们只需要知道,此时的S状态,是可中断睡眠!

D状态

睡眠状态(磁盘睡眠)不可被中断,不可被唤醒!

一个小例子,给大家解释清楚

今天在我们自己的服务器上,我们是无法演示D状态的,因此,通过上面一个小例子的解释,希望大家能明白什么是D状态即可

dd命令可以演示D状态,有兴趣的伙伴可以查一下如何去操作。

T状态

暂停或调试状态

可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可 以通过发送 SIGCONT 信号让进程继续运行。

X状态 

我们知道,当一个进程执行完之后,它的资源(task_struct结构体等)资源是需要回收的。

如果操作系统中存在大量的进程个需要同时终止,如果OS不能马上回收所有资源。

此时正在等待资源回收的进程就是X状态,注意:此时这个进程已经死了,它只是在等待回收而已

这是一个瞬时性的状态,我们很难在命令行中看到。

Z状态 僵尸状态

僵尸进程(Zombies)是一个大话题,我们想要完全理解他,需要从是什么?为什么?怎么办?三个角度进行理解。

今天博主将会带着大家理解前两个问号,第三个问号博主会专门再写一篇博客进行讲解!

僵尸状态:当一个进程,死掉的时候,不能被OS立刻清理,还在被检测,被调查死因的时候的状态,叫做僵尸状态!

僵死状态(Zombies)是一个比较特殊的状态。

当进程退出并且父进程(使用wait()系统调用,后面讲) 没有读取到子进程退出的返回代码时就会产生僵死(尸)进程

僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。

所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态

模拟僵尸状态的一个代码

#include <stdio.h>
#include <stdlib.h>
int main() {
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return 1;
    }
    else if(id > 0)
    { 
        //parent
        printf("parent[%d] is sleeping...\n", getpid());
        sleep(30);
    }
    else
    {
        printf("child[%d] is begin Z...\n", getpid());
        sleep(5);
        exit(EXIT_SUCCESS);
    }
    return 0; 
}

运行结果:

僵尸进程危害

进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!

维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护!

那一个父进程创建了很多子进程,就是不回收,就会造成内存资源的浪费!因为数据结构对象本身就要占用内存,如C语言的结构体,是需要开辟空间的!

如果是C++,析构函数一直都没有被调用!

因此会造成内存泄漏!

如何避免?我们以后再讲,这里不展开了了。

讲到这里,我们所有值得关注的进程状态就已经全部讲完了!下面我们来看看另一种进程

孤儿进程

孤儿进程

僵尸进程是子进程先退出。

那如果父进程先退出,子进程就称之为“孤儿进程” !

孤儿进程不能没有父进程!没有父进程谁来回收他的资源呢?因此它会被被1号init进程领养,由1号进程(init 即系统本身)回收!

代码如下:

让父进程先结束 

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return 1; 
    }
    else if(id == 0)
    {
        //child
        printf("I am child, pid : %d\n", getpid());
        sleep(10);
    }
    else
    {
        //parent
        printf("I am parent, pid: %d\n", getpid());
        sleep(3);
        exit(0);
    }
    return 0; 
}

运行结果:

这样,我们就验证了我们的结论。

总结 

讲到这里,关于进程状态,我们已经有了一定了解了。但是关于Linux OS的进程,还有很多知识点,比如地址空间,优先级等等。博主是很早之前学习了这部分的内容,写博客也是博主的一种复习,因此操作系统的其他内容,博主都会总结并分享学习心得的。希望大家点点赞点点关注,你们的支持是我最大的动力!

( 转载时请注明作者和出处。未经许可,请勿用于商业用途 )
更多文章请访问我的主页

@背包https://blog.csdn.net/Yu_Cblog?type=blog

posted @ 2023-01-08 01:41  背包Yu  阅读(22)  评论(0编辑  收藏  举报  来源