【Linux】进程状态的理解

简介: 【Linux】进程状态的理解

概述


引入

其实在我们运行程序时,CPU并不是一个进程一直在运行,而是一个进程跑一会,另一个进程跑一会,这样间替周而复始。----- 分时操作系统

那凭什么要运行这个进程而不是另一个? 答案就取决于 进程状态


我们来看一下在Linux内核源代码中是怎么定义进程状态的:

/*
* 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 */
};


可以看到状态态在kernel源代码里定义的方式大概是用一个数组包装,用不同的数字来标识不同的状态,如 0 代表R(运行)状态


R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里

S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))

D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。

T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。注意还有个小写t 对应的是追踪暂停状态

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


因为操作系统有很多,每个操作系统对进程的管理都有所差别,但基本原理是一样的,我们下面来研究Linux操作系统来观察进程状态


两个先行概念


阻塞:进程因为等待某种条件就绪,而导致的一种不推进的状态 ---- 进程卡住了 ---- 一定是在等待某种资源 ( 进程等待某种资源就绪的过程


就比如我写了个scanf代码,当CPU运行到此处时,需要等待我从键盘进行输入,此时进程的状态就是阻塞状态。


为什么程序多会卡? 因为启动了太多的进程,操作系统难以调度过来,进程卡住了(阻塞!)
为什么要有阻塞? 进程要通过等待的方式,等具体的资源被别人用完之后,再被自己使用

挂起:闲置进程的代码和数据被交换到磁盘中(可以看作成一种特殊的阻塞状态)

注:


阻塞就是不被操作系统调度,一定是当前进程需要等待某种资源就绪,一定是task_struct结构体需要在被某种OS管理下的资源下进行排队


我们为啥创建进程


因为我们想要让进程帮我们办事情。进程给我们办事情我们就会关心结果,为了使程序能并发执行,且对并发执行的程序加以描述和控制(PCB),我们引出了进程的概念。


计算机在开机的时候,操作系统就会被加载到内存里面,磁盘中的程序在运行的时候也会被加载到内存里面,实际上是加载到操作系统内部,受操作系统的管理,我们知道程序运行的时候,是需要CPU进行读取进程的代码并计算的,但进程的数量一定会比CPU多,那CPU该怎么一个个的读取进程代码并计算呢?答案是通过运行队列(PCB数据结构)来对进程的运行进行管理。


Linux下的进程状态


我们可以通过测试代码进行观察


1. R 运行状态

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 
  5 int main()
  6 {                                              
  7    while(1) 
  8     {}    
  9 }


当程序代码只是一个死循环时,我们将程序运行起来,然后查看进程状态,可以很明显的看到状态是R,也就是运行状态。


617dc131f1d54065a557b6ceb694c5ae.png


问题1:进程只要是R状态,就一定是在CPU上运行吗?

进程是什么状态,一般也看这个进程在哪里排队 。

也就是说,R状态并不直接代表进程在运行,而代表进程在运行队列中排队。

问题2进程队列是谁维护的?

操作系统自己维护的(即内存中),并不是CPU内维护。运行队列中是task_struct (PCB控制块)


2.S 休眠状态 — 可中断休眠状态


如果我们在刚刚代码中加入一句printf实现在显示屏上打印再来观察进程状态会出现什么?

b9a309fbac384ef68a37b9d2f5cb3d07.png


结果

5f8523f52e72498eb22c0f6c76c285b6.png


我们在代码中添加printf语句之后,程序还在运行,但观察到的进程的状态是S休眠状态,这是为什么呢?

因为我们的代码中访问了外设资源,即显示器。我们知道CPU会飞速的运行完进程的所有代码,但是我们写的进程需要占用硬件资源,每一次占用硬件资源都要等显示器就绪,这会花很长的时间(CPU计算的速度和IO的速度差别大概是几十万倍)。大概率99%的时间是进程在等显示器就绪,也就是在等待IO就绪,1%的时间是CPU在运行进程的代码,所以我们在查看进程状态的时候,极大概率上查到的都是S休眠状态。更形象化的说明就是,在进程访问完毕一次显示器的时候,CPU已经将这个死循环代码执行了50、60万次,所以我们在查看进程状态的时候,进程都是在等IO就绪的,所以就会查看到进程是休眠状态,这也是阻塞状态的一种。

S 休眠状态的两种

S 休眠状态本质是一种阻塞状态

S+: 前台运行 可以ctrl C暂停

S:后台运行 不可以Ctrl C暂停 可以直接kill 干掉


3. D 磁盘休眠状态 —不可中断休眠


S状态是浅度睡眠状态,是可以被终止的,通过ctrl+c或kill -9 pid两种方式进行分别进行前后台终止。

背景

当阻塞进程过多时,操作系统会将一些进程挂起,以此来解决内存空间不足的问题。如果挂起依旧无法解决内存空间不足,Linux操作系统就会将进程杀死,但是这样杀死进程很有可能导致进程对应的IO过程失败,从而丢失大量数据,这会对用户造成巨大的损失,所以就出现了一个新的进程状态,深度睡眠状态,这样的进具有无法被操作系统kill掉的特性。一般情况下,处于D状态的进程,只能等待IO过程结束,让进程自己醒来,重新投入CPU的运行队列,重新继续运行进程。或者万不得已可以通过断电的方式来杀掉深度睡眠的进程!!!

当然深度睡眠的状态一般不会出现,只有高IO的情况下,运行某个程序时,进程才有可能出现深度睡眠的状态。如果处于这种状态,计算机也宕机了。


4.T 暂停状态 (t 追踪暂停状态)


本质是阻塞状态的一种。

我们可以通过kill命令进行进程暂停状态的操作

kill -19 + 进程id --- 停止运行进程
kill -18 + 进程id --- 继续运行进程



675ec44d827a41d1a337d720cb192e46.png

注:状态后面带+,表示前台进程,状态后面不带+,表示后台进程


t 追踪暂停状态


Linux内核源代码中为了区分跟踪暂停和暂停状态,将T改为t来表示追踪暂停状态

适用在我们调试断点处 本质:暂停进程

当我们在调试某个二进制程序的时候,其实就是在调试该进程,当进程中有断点的时候,gdb中按下r进行调试运行,此时就会由于断点的存在而停下来,这其实表示的就是我们当前运行的进程停下来了,等待我们查看当前进程的上下文数据,这就是tracing stop状态,跟踪状态。


5. X 死亡(瞬时)状态


当进程结束时会显示X 状态,由于CPU处理的速度之快,我们极难观察到。


6. Z 僵尸进程


想一下如果一个进程退出了,立马进入X状态,你作为父进程,有没有机会拿到退出结果呢?

僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)

没有读取到子进程退出的返回代码时就会产生僵死(尸)进程。

所以Linux当进程退出的时候,一般进程不会立即彻底退出,而是要维持一个状态叫做:Z 僵尸状态。—方便后续父进程(OS)读取该子进程退出的退出结果。

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

代码示例:


  1 #include  <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 
  5 int main()
  6 {
  7 
  8     pid_t id = fork();
  9 
 10     if (id==0)
 11     {
 12         printf("I am a child process,pid:%d,ppid:%d\n",getpid(),getppid());
 13         sleep(5);
 14         exit(1);
 15     }
 16     else
 17     {
 18         while(1)
 19         {
 20             printf("I am a parent process,pid:%d,ppid:%d\n",getpid(),getppid());
 21             sleep(1);                                                                
 22         }
 23     }
 24     return 0;
 25 }

结果:


400d4f62d59b49489fa44d253100a82c.gif


僵尸进程危害


那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!


进程的退出状态属于进程的基本信息,也是需要数据进行维护的,所以这种信息会被保存在进程对应的PCB里面,如果进程的状态一直是Z状态的话(父进程一直不读取子进程的退出状态),那么PCB就需要一直维护这种状态信息,虽然子进程对应的代码和数据会被释放,但是PCB是不会被释放的,因为他需要维护进程的Z状态,所以这个时候就会产生内存泄露的问题

僵尸进程会产生资源泄露,需要避免避免僵尸进程的产生采用进程等待(wait/waitpid)方式完成


僵尸进程无法被杀掉,即使通过kill信号也无法杀掉,因为它已经死亡了,所以无法被杀掉的进程有三个,深度睡眠进程,僵尸进程,死亡进程,D状态是不能杀,Z和X是无法杀,因为已经死了! 😳


在结束进程之后,操作系统拿到退出码,进行甄别帮我们将父进程和子进程一起回收掉。以免内存泄露的发生。


7. 孤儿进程


如果父进程提前退出,那么子进程就被称之为:孤儿进程

我们知道僵尸进程是子进程退出,父进程还在运行,由于父进程需要读取到子进程状态,子进程会进入Z(僵尸)状态,后续会由操作系统进行甄别回收。那么子进程退出,父进程会帮助子进程进行回收。如果父进程提前退出了,那么它的子进程谁来回收?答案是子进程会被OS自动领养(通过1号进程(bash) 成为其新的父进程)

孤儿进程:父进程先于子进程退出,子进程成为孤儿进程,运行在后台,父进程成为1号进程(而孤儿进程的退出,会被1号进程负责任的进行处理,因此不会成为僵尸进程)

#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;
}


5368cdb5cf084fb69de2ed6ecd840657.png



为什么要领养子进程呢? 如果不领养,未来孤儿进程退出,谁来替它收尸(回收进程)?这样就会产生内存泄漏问题。


相关文章
|
1月前
|
Shell Linux 调度
【Shell 命令集合 系统管理 】Linux 调整进程优先级 renice命令 使用指南
【Shell 命令集合 系统管理 】Linux 调整进程优先级 renice命令 使用指南
41 0
|
29天前
|
消息中间件 Linux 调度
【Linux 进程/线程状态 】深入理解Linux C++中的进程/线程状态:阻塞,休眠,僵死
【Linux 进程/线程状态 】深入理解Linux C++中的进程/线程状态:阻塞,休眠,僵死
67 0
|
1天前
|
消息中间件 Unix Linux
Linux的学习之路:17、进程间通信(1)
Linux的学习之路:17、进程间通信(1)
13 1
|
6天前
|
算法 Linux 调度
深入理解Linux内核的进程调度机制
【4月更文挑战第17天】在多任务操作系统中,进程调度是核心功能之一,它决定了处理机资源的分配。本文旨在剖析Linux操作系统内核的进程调度机制,详细讨论其调度策略、调度算法及实现原理,并探讨了其对系统性能的影响。通过分析CFS(完全公平调度器)和实时调度策略,揭示了Linux如何在保证响应速度与公平性之间取得平衡。文章还将评估最新的调度技术趋势,如容器化和云计算环境下的调度优化。
|
7天前
|
监控 Linux
linux监控指定进程
请注意,以上步骤提供了一种基本的方式来监控指定进程。根据你的需求,你可以选择使用不同的工具和参数来获取更详细的进程信息。
14 0
|
8天前
|
消息中间件 监控 Linux
Linux进程和计划任务管理
通过这些命令和工具,你可以有效地管理Linux系统中的进程和计划任务,监控系统的运行状态并保持系统的稳定和可靠性。 买CN2云服务器,免备案服务器,高防服务器,就选蓝易云。百度搜索:蓝易云
101 2
|
11天前
|
算法 Linux 调度
深度解析:Linux内核的进程调度机制
【4月更文挑战第12天】 在多任务操作系统如Linux中,进程调度机制是系统的核心组成部分之一,它决定了处理器资源如何分配给多个竞争的进程。本文深入探讨了Linux内核中的进程调度策略和相关算法,包括其设计哲学、实现原理及对系统性能的影响。通过分析进程调度器的工作原理,我们能够理解操作系统如何平衡效率、公平性和响应性,进而优化系统表现和用户体验。
20 3
|
15天前
|
监控 Java Linux
linux下监控java进程 实现自动重启服务
linux下监控java进程 实现自动重启服务
|
16天前
|
监控 Linux Shell
初识Linux下进程2
初识Linux下进程2
|
16天前
|
Linux 编译器 Windows
【Linux】10. 进程地址空间
【Linux】10. 进程地址空间
19 4