【Linux】进程状态

目录

一、引言

1、孤儿僵尸2:41:50讲解如何通过结构体的某一个成员的地址找到结构体的地址

二、进程状态

1、状态是什么?

2、状态决定了什么?

3、运行状态

4、阻塞状态

5、挂起状态

三、具体介绍Linux中的进程状态

1、R状态

通过代码查看R状态:

2、前台进程和后台进程

3、S状态

4、D状态

5、T状态

6、t状态:

7、Z状态:僵尸状态

8、为什么要有Z状态?

9、X状态

10、孤儿进程


前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家

点击跳转到网站

一、引言

首先进程不是一直在运行的,就算是将进程放到CPU上也不会一直在运行(有时间片的影响)。

cpp 复制代码
  1 #include<stdio.h>
  2 int main()
  3 {
  4     int a = 0;
  5     scanf("%d",&a);
  6     printf("%d",a);
  7     return 0;
  8 }    

比如运行上述代码时:

此时就会卡住,因为有scanf函数的存在,这个进程就需要等待键盘资源。
只要进程在排队,那一定是在等待某种"资源",并且只要是在排队,那一定是进程的task_struct进行排队。

1、孤儿僵尸2:41:50讲解如何通过结构体的某一个成员的地址找到结构体的地址

一个task_struct可以被连入多种数据结构中,这是如何办到的?

实际是将某种数据结构的对象作为task_struct的成员变量,然后链接的时候,是链在这个对象上面,然后通过这个对象的地址和偏移量就可以找到task_struct结构体的地址:

对象变量地址 = 结构体地址 + 偏移量

二、进程状态

1、状态是什么?

在很多教材中对进程状态的表示一般是:运行、阻塞、挂起。

实际上进程状态其实就是一个整型变量,并且是在task_struct中的一个整型变量

2、状态决定了什么?

状态决定了进程的后续动作:Linux中可能会存在多个进程都要根据它的状态执行后续动作(比如进程排队)。

3、运行状态

每个CPU都有一个对应的运行队列。处于该运行队列中的进程都在等待CPU调度,此时进程的状态就是运行状态

4、阻塞状态

比如在上述的例子中:

cpp 复制代码
  1 #include<stdio.h>
  2 int main()
  3 {
  4     int a = 0;
  5     scanf("%d",&a);
  6     printf("%d",a);
  7     return 0;
  8 }     

当运行到scanf语句的时候,该进程就需要等待键盘的资源,此时进程的状态就由运行状态转换为阻塞状态。

当我们的进程在进行等待软硬件资源(比如上述等待键盘资源)的时候,资源如果没有就绪。进程的task_struct只能做两件事:

(1)、将自己设置为阻塞状态;

(2)、将自己的PCB连入等待的资源提供的等待队列中(比如上述进程会连入键盘的等待队列中);

每个设备都有一个自己的等待队列(例如键盘),其次CPU也属于设备,它有一个运行队列。进程状态的变化,引起的是PCB会被操作系统变迁到不同的设备的等待队列中。

问题:硬件的就绪状态只有谁最清楚?

操作系最清楚,因为操作系统是软硬件的管理者。

5、挂起状态

挂起状态有个前提:此时计算机资源已经比较吃紧了。

通俗来讲,当一个进程阻塞了,如果此时计算机的内存特别的吃紧,那么就会将该进程的代码和数据拷贝到外设磁盘,然后把内存中的数据释放掉(这个过程叫唤出,但注意内存中释放的是代码和数据,不会释放该进程的PCB,因为PCB要进行管理),此时进程的状态就是挂起状态(也叫阻塞挂起),在内存需要的时候,又将代码和数据从外设磁盘拷贝到内存(这个过程叫唤入)。

注意事项:

三、具体介绍Linux中的进程状态

这是Linux内核源码内容:

下面依次介绍

1、R状态

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

通过代码查看R状态:

cpp 复制代码
 //通过代码查看R状态
  6     while(1)
  7     {
  8         printf("i am a process\n");
  9         sleep(1);
 10     }

运行这段代码:

通过指令查看进程信息:

cpp 复制代码
ps ajx | head -1 && ps ajx | grep myprocess 

STAT一栏就表示进程的状态,但我们已经运行了该程序,为什么还是S+(睡眠)状态呐?

如下:

此时如果我们把多余的代码全部注释,只剩下一个while循环,此时就不访问任何外设,所以看到状态就为R

2、前台进程和后台进程

(1)、前台进程:在上面我们看到状态的后面都有一个 + 号,如R+

这代表此时这个进程叫前台进程,前台进程与用户直接交互,会占用当前终端,在它运行期间,用户无法在同一终端输入其他命令(如pwd),直到该进程结束。

前台进程可以通过键盘输入(如Ctrl+C组合键)来终止进程,也可以使用Ctrl+Z组合键将其暂停并放入后台。

(2)、后台进程:当在程序时,在后面加上一个"&",这样形成的进程就叫后台进程,在后台运行,不会占用终端,用户可以在同一终端继续输入和执行其他命令:

通常使用kill命令来管理,如kill -9 <PID>可强制终止指定进程,键盘组合键是终止不掉的。

3、S状态

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

cpp 复制代码
 //查看S状态
  6     int a = 0;
  7     scanf("%d",&a);
  8     printf("%d\n",a);

当等待scanf输入时,此时进程就是S+状态:

所以其实S状态就是阻塞状态,又因为ctrl+c可以中断S状态的进程,所以S状态也叫可中断睡眠(浅层睡眠)。

4、D状态

D:磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
有时候把操作系统逼急了,是会直接杀掉进程的,而D状态相当于给进程赋予了一个"免死金牌",是该进程不会被操作系统杀掉。(注意D状态也是属于阻塞状态的一种)。

5、T状态

T:停止状态(stopped) 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
首先通过 kill -l可以查看kill的信号分类:

然后可以看到信号19,信号19就可以使进程暂停,即进入T状态:

信号18可以重新启动一个进程:

此时可以注意到S+变成了S,也就是说通过暂停和启动这一个过程,一个前台进程会默认变成后台进程。

6、t状态:

t状态也是暂停的意思:
当我们调试的时候,程序运行到断点处,此时程序对应的进程就是t状态


7、Z状态:僵尸状态

Z状态:也叫僵尸状态,一个进程已经执行完毕,但父进程还没有获取它的退出信息,此时进程的状态就叫僵尸状态。
查看僵尸状态的示例代码:

cpp 复制代码
   //查看僵尸状态的代码
  8     pid_t id = fork();
  9     if(id == 0)
 10     {
 11         int cnt = 5;
 12         while(cnt)
 13         {
 14             printf("i am a child,pid: %d,ppid:%d\n]",getpid(),getppid());
 15             sleep(1);
 16             cnt--;
 17         }
 18         exit(0);//让子进程直接退出
 19     }
 20     //父进程
 21     while(1)
 22     {
 23         printf("i am a father, pid:%d,pptd:%d\n",getpid(),getppid());
 24         sleep(1);
 25     }

首先创建一个子进程,然后通过exit让子进程直接退出,但后面父进程并没有读取子进程的退出信息,所以子进程就变为了Z+状态。

8、为什么要有Z状态?

因为我们创建进程是希望这个进程帮用户完成相关工作,子进程必须得有PCB中的结果和数据。什么是Z?进程已经退出,但是当前进程的状态还需要自己维持住,供上层读取,必须是Z状态。此时代码和数据可以被释放,但PCB不能被释放。
如果父进程不读取退出信息,僵尸状态的进程会一直存在,task_struct对象也就会一直存在,但我们并不需要,这就会占据内存,造成内存泄漏。

9、X状态

X状态就是父进程读取了子进程的退出信息后,子进程彻底终止,最终死亡。

10、孤儿进程

孤儿进程:通俗讲就是父进程比子进程优先终止的子进程,这样该子进程的退出信息就没人读取,这样该进程就不会最终退出,从而造成内存泄漏,这样的进程也叫孤儿进程。
操作系统为了防止孤儿进程造成的内存泄漏,所以有了如下处理方法:

查看孤儿进程的示例代码:

cpp 复制代码
//查看孤儿进程
  8        pid_t id = fork();
  9        if(id == 0)
 10       {
 11           int cnt = 500;
 12           while(cnt)
 13           {
 14               printf("i am a child,pid: %d,ppid:%d\n]",getpid(),getppid());
 15               sleep(1);
 16               cnt--;
 17           }
 18           exit(0);//让子进程直接退出
 19       }
 20       //父进程
 21       int cnt = 5;
 22       while(cnt)
 23      {    
 24          cnt--;
 25           printf("i am a father, pid:%d,pptd:%d\n",getpid(),getppid());
 26           sleep(1);
 27      }
 28     


相关推荐
老秦包你会2 分钟前
Linux课程五课---Linux进程认识1
linux·运维·服务器
渭雨轻尘_学习计算机ing7 分钟前
二叉树构建算法全解析
算法·程序员
浩浩测试一下19 分钟前
网络安全实战指南:从安全巡检到权限维持的应急响应与木马查杀全(命令查收表)
linux·安全·web安全·ubuntu·网络安全·负载均衡·安全架构
我想吃余42 分钟前
Linux学习笔记(一):Linux下的基本指令
linux·笔记·学习
刘某的Cloud1 小时前
openstack迁移虚机rbd报错,删除异常rbd
linux·运维·openstack·nova·rbd
C语言魔术师1 小时前
70. 爬楼梯
算法·动态规划
啊吧怪不啊吧1 小时前
Linux权限概念讲解
linux·运维·服务器
跳跳糖炒酸奶2 小时前
第二章、Isaaclab强化学习包装器(1)
人工智能·python·算法·ubuntu·机器人
努力努力再努力wz2 小时前
【Linux实践系列】:进程间通信:万字详解命名管道实现通信
android·linux·运维·服务器·c++·c
许_安2 小时前
leetcode刷题日记——两数相加
算法·leetcode·职场和发展