当前位置: 首页 > news >正文

【Linux系统编程】进程概念(三)进程状态

【Linux系统编程】进程概念(三)进程状态

  • 1. 操作系统教材中的进程状态
    • 1.1 运行状态
    • 1.2 阻塞状态
    • 1.3 挂起状态
  • 2. Linux操作系统的进程状态
    • 2.1 R运行状态
    • 2.2 S睡眠状态
    • 2.3 磁盘休眠状态
    • 2.4 T停止状态
    • 2.5 t追踪停止状态
    • 2.6 进程状态查看
    • 2.7 前台和后台进程
    • 2.8 X死亡状态
  • 3 僵尸进程(Z僵尸状态)
    • 3.1 僵尸进程的危害
  • 4. 孤儿进程

1. 操作系统教材中的进程状态

在这里插入图片描述
别看图中写了很多状态,其实主要就是三种状态:运行状态、阻塞状态、挂起状态。

下面具体说说这三种状态。

1.1 运行状态

在这里插入图片描述

  • 一个CPU对应一个运行队列,在该运行队列中的进程,遵循FIFO(先进先出)的原则,排在前面的进程先被运行。
  • 凡是在这个队列中的进程,状态都是运行状态。

1.2 阻塞状态

当我们运行一个有scanf的C程序的,程序会卡在scanf那里,它需要键盘上面的数据才能运行,否则就会一直卡在这里,键盘是硬件,而操作系统是硬件的管理者,操作系统肯定不能一直等这个进程,它还有别的进程需要调度,所以操作系统就会把该进程先托管到该硬件上,等该硬件就绪,这里就是键盘数据输入完成,再把该进程重新加入到运行队列中。

int main()
{int a = 0;scanf("%d", &a);printf("a = %d\n", a);return 0;
}

在这里插入图片描述

  • 阻塞与运行的本质:看你的task_struct在谁提供的队列中。

1.3 挂起状态

如果操作系统(OS)发现自己的内存空间不足了,那OS就会把一些进程的代码和数据交换到swap分区(磁盘),即swap out(换出),此时该进程处于阻塞挂起状态,如果这样做还是不行,LinuxOS就会选择性的杀掉特定的进程,等到OS发先自己的内存足够了就会把swap分区中该进程的代码和数据拿回来,即swap in(换入)。

在这里插入图片描述

  • 磁盘中的swap分区存在的本质是用时间换空间。
  • swap分区的大小一般是内存的1.5~2倍,不建议swap分区太大,这样会导致OS会过度依赖swap分区,只要有进程就把它的代码和数据交换到swap分区,而过度的swap out/in,会导致系统变慢。

2. Linux操作系统的进程状态

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,僵死};

2.1 R运行状态

R运行状态:并不意味着进程一定在运行中,它表明进程要么在CPU运行中,要么在CUP对应的运行队列里。

我们以下面的代码为例,讲解一下R运行状态。

#include <stdio.h>int main()
{while(1){}return 0;
}

运行结果:
在这里插入图片描述

我们看到该进程的STAT是R+,R就是运行状态,+表示该进程在前台,什么也没有表示该进程在后台,后面会讲前台和后台。

在这里插入图片描述

结合我们最开始讲的运行状态,思考一下为什么该进程处于运行状态?

因为该进程一直在死循环重复执行代码,占用CPU资源,并且它不满足访问外设,例如显示屏、键盘等,进入阻塞状态。

2.2 S睡眠状态

S睡眠状态(sleeping):意味着进程在等待事件完成,同时也叫做可中断睡眠,浅度睡眠,即睡眠期间可以被唤醒,对应操作系统学科中的阻塞状态。

我们以下面的代码为例,讲解一下S睡眠状态。

#include <stdio.h>    int main()    
{    while(1)    {    printf("I am process\n");                                                                 }                                   return 0;                           
}   

运行结果:
在这里插入图片描述

我们看到该进程的STAT是S+,此时该进程就处于睡眠状态。

在这里插入图片描述

那么我们思考一下为什么该进程会处于睡眠状态?

由于我们一直用printf打印,要访问外设显示器,在写入显示器完成的一瞬间将其加入到运行队列,等待运行,此时该程序处于运行状态,只不过时间太短,我们很难查看到,之后再托管到显示器中进行写入,所以我们查看的时候一般都会显示该进程处于休眠状态。

但是如果是下面的程序,只要我们不在键盘中输入数据,该进程就一直处于睡眠状态。

int main()
{int a = 0;scanf("%d", &a);printf("a = %d\n", a);return 0;
}

在这里插入图片描述
在这里插入图片描述

2.3 磁盘休眠状态

D磁盘休眠状态(disk sleep):也叫做深度睡眠,不可中断睡眠状态,Linux特有的状态,处于这个状态的进程通常会等待IO的结束,不响应任何请求,同时其也对应操作系统学科上的阻塞状态的一种特殊情况,处于深度睡眠的进程不可被信号,或者OS杀掉。

想象这样一个场景:一个进程正准备向磁盘写入一批重要数据。

  1. 危机潜伏:磁盘的剩余空间可能不足以容纳这批数据,但进程并不知情,它发起了写入请求后,便进入等待状态,期盼着磁盘的“操作成功”回复。
  2. 系统发难:恰在此时,操作系统发现内存资源告急,整个系统濒临卡顿。为了自救,它开始清理“占用资源却不干活”的进程。它一眼就看到了这个正在“悠闲”等待磁盘回复的进程,心想:“内存都快耗尽了,你居然还在空等?” 于是,操作系统手起刀落,强制终止了这个进程,并回收了其占用的所有内存,其中自然也包括那批等待写入的数据。
  3. 磁盘的抉择:几乎在同一时间,磁盘正在处理写入请求。它发现空间确实不够,无法完成这个任务。磁盘心想:“这批数据太大了,我写不进去。但我剩余的空间还可以服务其他进程的小数据请求,不能让它堵在这里。” 于是,磁盘丢弃了这批无法写入的数据,转而处理其他任务。当它回过头来,准备给刚才的进程回复一个“写入失败”时,却发现——那个进程已经消失了

结果:数据彻底丢失了。进程的数据在内存中被系统释放,在磁盘端又被丢弃。用户的重要资料就此不翼而飞。

责任在谁?

  • 操作系统:为了保障系统整体稳定而清理资源,看似无可厚非。
  • 磁盘:为了不阻塞其他任务而丢弃无法处理的数据,似乎也情有可原。
  • 进程:只是一个等待结果的“受害者”。

这成了一笔糊涂账。但用户绝不接受这种结果,频繁的数据丢失将是灾难性的。

解决方案:引入“深度睡眠”状态

为了解决这一致命问题,深度睡眠状态应运而生。当进程在执行诸如关键数据写入这类不容中断的任务时,它会被标记为此状态。

一旦进入深度睡眠,进程便获得了一把“尚方宝剑”:

  • 对操作系统的清理指令置若罔闻,即使是强大的 kill -9 信号也无法将其终止。
  • 它仿佛进入了一个受保护的“结界”,其代码和数据会一直被保留在内存中,纹丝不动

进程会一直维持这种“不死”的沉睡,直到它等待的事件发生——也就是磁盘确实给出了最终回复(无论成功与否)——它才会被唤醒并做出响应。

这样一来,在上述场景中,即使内存紧张,操作系统也无法杀死这个进程。磁盘在丢弃数据后发出的“失败”回应,最终也能顺利送达这个仍在等待的进程。进程收到回应后,便可以由内核将其正常唤醒和终止,或者尝试其他错误处理策略,从而从根本上避免了数据的无声丢失

2.4 T停止状态

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

#include <stdio.h>      
#include <sys/types.h>      
#include <unistd.h>      int main()      
{      while(1)      {      printf("I am process, pid:%d,ppid: %d\n", getpid(), getppid());      sleep(1);                                                                       }                                                             return 0;                                                     
}       

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

2.5 t追踪停止状态

t追踪停止状态(tracing stop):在调试过程中的进程停止。

在这里插入图片描述
在这里插入图片描述

2.6 进程状态查看

ps aux / ps axj 命令

  • a: 显示一个终端所有的进程,包括其他用户的进程。
  • x: 显示没有控制终端的进程,例如后台运行的守护进程。
  • j:显示进程归属的进程组ID、会话ID、父进程ID,以及与作业控制相关的信息。
  • u:以用户为中心的格式显示进程信息,提供进程的详细信息,如用户、CPU和内存使用情况等。

2.7 前台和后台进程

区分前后台进程:

谁能从键盘获取数据输入,谁就是前台。

键盘只有一个,所以在获取输入的时候,只能有一个进程在获取键盘数据。

前台进程任何时刻只能有一个,后台进程可以有很多个。
例如你自己的手机打开很多app,谁在你的屏幕上谁就是前台,其他的都在后台。

2.8 X死亡状态

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

3 僵尸进程(Z僵尸状态)

僵尸进程(zombies):子进程退出的时候,如果父进程没有主动回收子进程的信息,那么子进程会让自己一直处于Z僵尸状态,即对应子进程相关资源尤其是task_struct结构体不能释放。

我们以下面的代码演示一下僵尸进程。

#include <stdio.h>    
#include <sys/types.h>    
#include <unistd.h>    int main()    
{    printf("我是父进程:%d\n", getpid());    sleep(3);    pid_t id = fork();    if(id == 0)    {    //child    while(1)    {    printf("我是子进程:%d,我的父进程是:%d\n", getpid(), getppid());    sleep(3);    }    }    else    {    while(1)    {    printf("我是父进程:%d\n", getpid());    sleep(3);    }    } return 0;
}

输出结果:

在这里插入图片描述
每3秒打印一次状态。
在这里插入图片描述

杀死子进程,此时父进程还在运行,无法收回子进程中的信息,导致子进程处于Z僵尸状态。

在这里插入图片描述

3.1 僵尸进程的危害

1. 进程退出了,退出信息是什么?

main函数的返回值or收到的信号值,该信号值在该进程的task_struct结构体中。

2. 进程退出了,退出信息保存在哪里?

进程自己的task_struct结构体中。
w
3. 检测Z状态进程,回收Z状态进程,本质是在做什么?

获取子进程的退出数据。

4. 具体怎么回收?谁来回收?

父进程系统调用(OS)wait命令来回收子进程的信息。

在这里插入图片描述

5. 子进程必须回收!!!

假如有一个常驻进程一直在malloc空间,如果我一直不回收该进程,它就会一直处于Z状态,它就会占据大量内存空间,task_struck也不会被释放,从而导致内存泄漏。

如果对应的进程结束了,内存泄漏还在吗?

进程结束,系统会自动回收该进程,不会存在内存泄漏。但在实践中,进程一般都是死循环,即不会结束的,所以内存泄漏问题很重要。

4. 孤儿进程

孤儿进程:在父子进程中,父进程提前退出,子进程的父进程会被改为1号进程(操作系统),我们称该子进程被操作系统领养了,此时这个子进程(孤儿进程)就变成了后台进程,无法从键盘中获取数据,那么Ctrl + C就无法退出,只能用信号kill -9 PID将子进程杀死。

我们以下面的代码为例,讲解孤儿进程

#include <stdio.h>    
#include <sys/types.h>    
#include <unistd.h>    int main()    
{    // 创建子进程        pid_t id = fork();        if(id == 0)        {        //child        while(1)    {    printf("我是子进程, pid: %d, ppid: %d\n", getpid(), getppid());    sleep(3);    }                                                                                                           }    else    {    //parent        int cnt = 5;    while(cnt)    {    cnt--;    printf("我是父进程, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt);    sleep(3);    }    }    return 0;
}

在这里插入图片描述

父进程退出,子进程被1号进程领养,并变成后台进程。
在这里插入图片描述
后台进程无法获取键盘中的数据,所以Ctrl + C不能结束该进程。

在这里插入图片描述
使用信号kill -9 PID 杀死该进程。
在这里插入图片描述
在这里插入图片描述

为什么要被系统领养?

给孤儿进程退出进行善后,系统自动回收。

http://www.dtcms.com/a/572714.html

相关文章:

  • 360极速浏览器 安装猫抓插件的方法
  • 面向对象(上)-递归方法的使用
  • PPPOE实验
  • 【LeetCode 经典题解】:队列与栈的双向模拟——从原理到代码详解
  • 学习笔记5
  • 多线程 忙等待和线程等待的区别
  • 网站建设运维合同汽车城网站建设方案
  • 建立网站需要什么设备网站制作对公司的作用
  • C++类与对象:从入门到精通
  • UniApp 全局通知功能实现
  • uni-app开发安卓app时控制屏幕常亮不息屏
  • uniapp 小程序引入 uview plus 框架,获得精美的UI框架
  • 在被窝里使用笔记本电脑,容易损坏键盘?
  • Unix Domain Socket:构建高效本地进程间通信的完整指南
  • 如何创建“国学助手”GPT?
  • AWS Elastic Beanstalk中安装tesseract5.3.4版本
  • 实战:用Elasticsearch构建爬虫数据搜索引擎
  • 微网站建设及微信公众号长春自助建站软件
  • 修改图片网站卖房app十大排行榜
  • python-爬虫之beautifulsoup
  • Ubuntu 24.04 安装 FreeSWITCH 完整教程
  • LeetCode(python)——49.字母异位词分组
  • Redis 性能优化与故障排查指南
  • 24.java openCV4.x 入门-Imgproc之轮廓凸包与凹陷检测(形状识别)
  • IDEA 插件推荐
  • 虚拟 DOM(Virtual DOM)的工作原理及其性能优化机制
  • git详细使用教程
  • 北京工程工程建设交易信息网站和城乡建设部网站
  • soular零基础学习,如何通过工作台聚合TikLab所有工具链
  • 建立企业网站电商网站建设开题报告