说在前面
今天给大家带来操作系统中进程状态的详解。
本篇博主将通过从进程状态的广泛概念,深入到Linux操作系统详细的一些进程状态。在解释进程状态的过程中,博主还会穿插一些操作系统一些重要概念!本篇干货满满,请大家不要吝啬一键三连哦!
前言
那么这里博主先安利一下一些干货满满的专栏啦!
这里包含了博主很多的数据结构学习上的总结,每一篇都是超级用心编写的,有兴趣的伙伴们都支持一下吧!手撕数据结构https://blog.csdn.net/yu_cblog/category_11490888.html?spm=1001.2014.3001.5482
这里是STL源码剖析专栏,这个专栏将会持续更新STL各种容器的模拟实现。算法专栏https://blog.csdn.net/yu_cblog/category_11464817.html
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