【Linux】进程状态

简介: 【Linux】进程状态

🌎进程状态【下】




前言:

  在Linux操作系统中,进程状态是一个重要而又复杂的话题。了解进程状态可以帮助我们更好地理解操作系统的运行机制。那么话不多说,开启我们今天的话题。


🚀内核中的进程状态

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

  进程状态就是这些大写字母,他们具体的含义如下:

  • R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
  • S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。
  • D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
  • T停止状态(stopped)可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
  • X死亡状态(dead)这个状态只是一个返回状态,你不会在任务列表里看到这个状态。

  我们来一个一个认识这些 进程状态


🚀运行状态和睡眠状态

✈️运行状态

  我们随便写一个程序,并且打印其进程标识符:

1 #include<stdio.h>
  2 #include<unistd.h>
  3 
  4 int main()
  5 {
  6     while(1)
  7     {
  8         printf("I'm a process, id=%d\n", getpid());
  9         sleep(1);
 10     }
 11     return 0;
 12 }                                                                  
~

  再使用ps命令监控进程的运行状态,命令如下:

ps ajx | head -1 && ps ajx | grep myprocess#查看进程信息

  我们发现跑起来的进程全部都是 S+(加号的作用后面再谈) 状态,也就是 睡眠状态,但是我们的程序在一直跑,哪来的睡眠状态?

  仔细看代码,哦,原来是Sleep函数搞的鬼,CPU速度很快,而休眠一秒对CPU来说太久了,所以很难捕捉到进程的运行状态。那么我们把sleep函数注释掉:

  尽管我们把sleep函数注释掉了,但是通过监控,我们发现进程状态还是 S+ 状态。其实还是 因为CPU的速度太快了,而我们的printf()函数需要调用外设刷新到显示器上,而CPU实在是太快了,外设每次刷新,CPU都已经执行了很多次了,所以我们依旧很难捕捉到进程的运行状态。

  那么好,我直接把printf函数接注释掉,就单纯while死循环,这下总得显示运行状态了吧!

  现在这个进程什么也不干,也不访问外设,所以这次我们就能检测出进程状态为运行状态了。

  想必你也发现了,grep 进程也是运行状态,这是因为我们使用了grep命令一直在过滤,就像我们睁开眼就知道自己是清醒的一样。


✈️前后台进程

  我们来解释一下为什么我们看到的进程状态是 S+ 或者 R+?在Linux中,存在着 前台进程后台进程 之分。

  我们在命令行运行起来的程序一般都为前台进程,前台进程的进程状态一般都会带 ‘+’ 号。后台进程 一般为后台独立运行的进程,一般不接收终端的输入。

  前台进程只需要加上特殊符号,也可转化为后台进程,比如:

./myprocess & #特殊符号,表示将进程后台运行。

  这个时候,就将进程变为后台进程了,S+ 也变为了 S,但是这里又出现了一个问题,我们 Ctrl + C 终止不掉这个进程。

  其实,后台进程是不能用 Ctrl + C 直接杀死的,我们需要使用 kill -9 进程标识符来杀死进程:


✈️睡眠状态

  我们来看一段之前写过的代码:

#include<stdio.h>
int main()
{
  int a = 0;
  scanf("%d", &a);
  return 0;
}

  再使用上面的监控脚本来进行进程状态的查看:

  这里我们运行程序,程序卡在了scanf处,右侧监视显示状态为 S+ ,说明此时处于休眠状态。这时程其实就是操作系统里的阻塞状态!

  虽然S状态就是 操作系统里的阻塞状态,但是操作系统级别的阻塞状态可能不仅仅包含S状态。

  我在运行程序的时候并没有执行输入操作,反而直接 Ctrl +C 操作杀死了这个进程,所以我们的S状态也被称为 可中断睡眠。


🚀其他状态

✈️D状态

  D状态,英文叫做:Disk Sleep,即 深度睡眠,但是今天我们没有办法从Shell上演示,所以我就描述一下D状态的作用。

  在还没有D状态的时候,如果一个进程在向磁盘中写入200M的数据,这时,进程将200M数据丢给磁盘,接下来就等待磁盘的信号。

  但是这个时候操作系统可能会误判这个 “不作为” 的进程,便会将它杀死,这样以来前面写入的数据无进程接收也就作废了,这里仅仅是200M数据,如果更多呢?这一定是一个大问题,所以操作系统就引入了一种D状态,那么当进程再对磁盘进行写入的时候,OS再过来检查,发现是D状态,便不会出现误判的问题了。

  由以上的例子,我们也大概了解了D状态的作用,那么现在又有新的疑问了,D状态是阻塞状态吗?

  其实,上面进程等待硬件资源的过程也是阻塞过程,所以D状态也是阻塞状态!


✈️T状态
📚T(Stopped)状态

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

  我们再执行我们的程序:

1 #include<stdio.h>
  2 #include<unistd.h>
  3 
  4 int main()
  5 {
  6     while(1)
  7     {
  8         printf("this is a process, id=%d\n", getpid());
  9         sleep(1);                                                  
 10     }
 11     return 0;
 12 }

  我们还会使用到 kill 命令的其中一个信号:

  之前我们使用9号信号来杀死进程,现在我们使用19号新号 sigstop,用来停止一个正在运行的进程:

  此时进程的状态就变为了 T 状态。这个进程就被暂停了。

  此时我们可以使用18号信号来继续被终止的进程执行。

  但是我们发现,继续运行后的进程却变为了后台进程,所以我们只有通过 kill -9 来杀死这个进程。


📚T(tracing stop)状态

  为了观察 t 状态,我们依旧使用下面的代码:

#include<stdio.h>
int main()
{
  int a = 0;
  scanf("%d", &a);
  return 0;
}

  我们使用gdb调试工具进行调试,再第七行打了断点,再次r的时候我们就能从监控脚本看到我们进程的状态为t状态了。t状态就是 表示被调试器调停以便于追踪调试的作用

  其实 不论是T状态还是t状态,他们都是暂停等待某种资源,所以,他们也都属于阻塞状态!


✈️X状态

  X状态很简单,一般X状态表示为进程正常退出的状态,进程退出都是瞬时的,所以这个状态很难给大家演示出来,只需要知道 X状态为进程正常退出状态即可


🚀僵尸状态

  进程状态中有一个特殊的状态——僵尸状态,为了方便理解我来举一个例子:

  有一天,阿熊在网上看到了一双很想要的鞋,于是攒下生活费一口气买下了,在购买商品后,支付给商家钱款。
  但商家收到后却没有做出相应的反应,也没有给你发货。此时,你的金钱已经离开了你的账户,但商品却没有交付,你陷入了一种僵尸状态,既没有完成交易,也无法继续购买其他商品。

  这就好比进程终止,但其父进程还没有做出善后处理,导致进程成为了僵尸状态。只有当商家发货或者退款后,交易才算是彻底完成。

  下面我们就举一个例子让大家认识一下僵尸状态:

1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 
  5 int main()
  6 {
  7     pid_t id = fork();
  8     if(id < 0)
  9     {
 10         perror("fork error!");
 11         exit(-1);                                           
 12     }                                                       
 13                                                             
 14     if(id == 0)                                             
 15     {                                                       
 16         //child                                             
 17         int cnt = 5;                                        
 18         while(cnt--)                                        
 19         {                                                   
 20             printf("I'm child process, pid=%d, ppid=%d\n", getpid(), getppid());
 21             sleep(1);                                       
 22         }
 23     exit(0);                                                   
 24     }                                                       
 25     while(1) //father                                               
 26     {                                                       
 27         printf("I'm father process, pid=%d, ppid=%d\n", getpid(), getppid());   
 28     }
 29 
 30     return 0;                                                   
 31 }

  我们从监控脚本可以看到,当子进程退出的时候,子进程接下来的状态就变为了Z状态,其中在出现Z的行,后面跟着 <defunct>,其实就是失效的意思

  所以为什么要有Z状态呢?

  我们为什么要创建进程?是希望进程能给我们做一些事情,所以子进程必须得有结果和数据。 那什么是僵尸呢?进程已经退出,但是当前进程的状态需要自己维持住,供上层读取(一般是父进程),所以必须处于Z状态!

  为了验证子进程一定会处于Z状态,我们不妨做个实验:

1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<sys/wait.h>
  5 #include<sys/types.h>
  6 
  7 int main()
  8 {
  9     pid_t id = fork();
 10     if(id < 0)
 11     {
 12         perror("fork error!");
 13         exit(-1);
 14     }
 15 
 16     if(id == 0)
 17     {
 18         //child
 19         int cnt = 5;
 20         while(cnt)
 21         {
 22             printf("I'm child process, pid=%d, ppid=%d\n", getpid(), getppid());
 23             sleep(1);
 24             cnt--;
 25         }
 26         exit(0);
 27     }
 28     //father
 29     int cnt = 10;
 30     while(cnt)
 31     {
 32         printf("I'm father process, pid=%d, ppid=%d\n", getpid(), getppid());
 33         sleep(1);
 34         cnt --;
 35     }
 36     wait(NULL);
 37     printf("child already reclaim!\n");
 38     sleep(5);                                                                                                                   
 39 
 40     return 0;
 41 }

  我们使用到了 wait 函数接口,我们简单来看一下:

  此接口是 用来回收子进程退出后的数据和信息的接口,具体我们以后再谈。

  我们发现,子进程退出后确实维持了一段时间的僵尸状态,后面被父进程回收。

  但是如果父进程没有读取子进程的信息呢?

  这个时候,僵尸状态的进程就会一直存在。task_struct 对象也一直存在,需要占据内存空间,这就造成了 内存泄漏!


🚀孤儿进程

  进程中还有一种特殊进程——孤儿进程,就跟名字那样,没有父进程的进程,也就是只有子进程。

  如果一个父进程结束了,而子进程还没结束,那么这个子进程必然会变成僵尸状态,如果很多进程都变成了这种状态,子进程越来越多,系统绝对会挂的。

  我们来看看事实是否如此:

1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<sys/wait.h>
  5 #include<sys/types.h>
  6 
  7 int main()
  8 {
  9     pid_t id = fork();
 10     if(id < 0)
 11     {
 12         perror("fork error!");
 13         exit(-1);
 14     }
 15 
 16     if(id == 0)
 17     {
 18         //child
 19         int cnt = 10;
 20         while(cnt)
 21         {
 22             printf("I'm child process, pid=%d, ppid=%d\n", getpid(), getppid());
 23             sleep(1);
 24             cnt--;
 25         }
 26         exit(0);
 27     }
 28     //father
 29     int cnt = 5;                                                                                                                
 30     while(cnt)
 31     {
 32         printf("I'm father process, pid=%d, ppid=%d\n", getpid(), getppid());
 33         sleep(1);
 34         cnt --;
 35     }
 36 
 37     return 0;
 38 }

  我们发现并没像我们预期的那样,子进程变为僵尸进程,而是变为了休眠状态,我们不妨查看这个进程的ppid:

  我们发现其实这时候子进程的父进程依旧存在,并且父进程变为了bash!

  其实,在Linux中,如果一个父进程创建了子进程,但是父进程要先比子进程退出,这时候 子进程会被1号进程(操作系统)托管

  相信细心的小伙伴也注意到了,我在杀死孤儿进程的时候使用了kill命令,其实这也就说明 当一个子进程变成孤儿的时候,也变成了后台进程


📒✏️总结

  •  CPU为了知晓下一步进程该做何调整,于是需要很多种的进程状态,常见状态无外乎 休眠,运行,深度睡眠,暂停,死亡,僵尸。
  •  进程分为前台进程和后台进程,前台进程可以使用 Ctrl+C 杀死,但是后台进程需要使用 信号杀死,运行程序时可以 使用特殊符号变为后台进程
  •  僵尸进程会影响系统的效率,僵尸是因为 父进程没有接收子进程的资源数据,使得子进程成为僵尸。
  • 父进程要比子进程先退出,则这个子进程就变为了孤儿进程,并且由 bash 托管。

  创作不易,如果这篇文章能帮助到你,还望点个小小的赞呀~~

相关文章
|
8月前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
10月前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
321 32
|
6月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
259 67
|
5月前
|
Web App开发 Linux 程序员
获取和理解Linux进程以及其PID的基础知识。
总的来说,理解Linux进程及其PID需要我们明白,进程就如同汽车,负责执行任务,而PID则是独特的车牌号,为我们提供了管理的便利。知道这个,我们就可以更好地理解和操作Linux系统,甚至通过对进程的有效管理,让系统运行得更加顺畅。
150 16
|
5月前
|
Unix Linux
对于Linux的进程概念以及进程状态的理解和解析
现在,我们已经了解了Linux进程的基础知识和进程状态的理解了。这就像我们理解了城市中行人的行走和行为模式!希望这个形象的例子能帮助我们更好地理解这个重要的概念,并在实际应用中发挥作用。
114 20
|
4月前
|
监控 Shell Linux
Linux进程控制(详细讲解)
进程等待是系统通过调用特定的接口(如waitwaitpid)来实现的。来进行对子进程状态检测与回收的功能。
102 0
|
4月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
127 0
|
4月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
91 0
|
4月前
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
90 0
|
8月前
|
存储 网络协议 Linux
【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
329 34