Linux 进程状态

为了更好的去了解进程,本博客来看看进程的每一种状态

1.就绪,阻塞,挂起

一般的操作系统的进程状态可以分为就绪,阻塞,挂起三种状态。

就绪:

资源已经准备就绪,进入就绪队列,随时可以被调度。

阻塞:

进程在阻塞状态下,通常是因为它正在等待某些资源的就绪,比如等待 I/O 操作完成、等待文件锁定、等待网络响应等。进程在这个状态下并不会占用 CPU 时间,而是进入等待队列,直到所需的资源变得可用,才会被操作系统重新调度执行。

阻塞的原因:

  1. I/O 操作:例如,读取文件、网络请求、硬盘访问等。
  2. 资源等待:例如,等待 CPU 时间片、内存、锁等。
  3. 同步等待:例如,等待其他进程的信号或消息。

阻塞状态的表现:

  1. 阻塞状态的进程不能继续执行,直到它所等待的资源或事件发生。
  2. 阻塞的进程进入操作系统的等待队列,并不占用 CPU 资源。
  3. 操作系统根据不同的等待类型(I/O、锁等)将进程排入不同的队列。

例子:

假设你在使用某个应用程序,它需要从网络获取数据。在等待数据从服务器返回之前,进程就会进入阻塞状态。在此期间,它不会占用 CPU,而是等待数据返回。

挂起:

挂起是进程脱离就绪或者阻塞队列,同时不会被CPU调度的一种表现,操作系统挂起进程的核心是优化资源利用率、支持多任务协作或保障系统稳定性。

挂起的原因:

  1. 用户手动挂起进程:用户通过终端或工具主动暂停进程,方便后续恢复。例:Linux中 Ctrl+Z 发送 SIGTSTP 信号,将前台进程置为停止态(T状态)
  2. 系统调度优化:内核调度器为提升整体效率,主动挂起低优先级进程 例:采用"抢占式调度"的系统中,高优先级进程就绪时,会挂起当前运行的低优先级进程;CPU负载过高时,挂起部分非核心进程,保障系统服务稳定。
  3. 调试或异常处理:调试器(如gdb)发送 SIGSTOP 信号,将目标进程置为跟踪态(T状态),方便逐行调试;

挂起状态的表现:

  1. 脱离CPU调度队列:挂起进程不会出现在CPU就绪队列中,调度器不会分配CPU时间片,
  2. 上下文完整保留:进程的PCB(进程控制块)、内存空间、寄存器数据、程序计数器等均被保留,唤醒后可无缝恢复执行(如同暂停视频后继续播放)。
  3. 执行停滞:进程的代码不再推进,不会进行任何运算或I/O操作(除非是挂起前已发起的异步I/O,如磁盘读写,完成后会唤醒进程)。
  4. 资源占用不变:进程仍占用已申请的内存、文件句柄、网络连接等资源,不会释放(区别于"终止态"),如果内存资源紧张,操作系统会进行内存的换入换出。

ps:内存不足是不会把进程挂起的,内存的换入和换出和进程的挂起是两个概念。

2. 看看Linux源码对于状态的定义

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

操作系统为了管理那些已经就绪,等待被cpu处理的进程,有一个运行队列的结构体,来管理这些已经就绪的进程,当一个进程已经就绪后,就会被放入到运行队列里面,排队等待cpu处理,但是不是非要把一个进程执行完毕后,才从cpu上面剥离,会有时间片的概念,时间片到了,就从cpu上面剥离下来,cpu处理下一个进程,进程被拿上去,拿下来的动作叫做进程切换。

S睡眠状态(sleeping): 意味着进程在等待资源就绪(这里的睡眠有时候也叫做可中断睡眠

(interruptible sleep))。

像上面的代码中,代码运行时,其实有大部分都在等待显示器资源的就绪,所以大部分时间都处于阻塞状态(如果没有printf,而是只有循环的话,那就会一直处于运行状态)。

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

这里先引入一个概念,当进程还没有被CPU调度时,由于内存资源的不足,操作系统可能会把进程的PCB保留,把代码和数据交换到外设,这叫做换出,当需要使用时,在加载到内存中,这叫做换入。

当内存资源严重不足时,进行了换入换出操作时,内存资源还是不足时,操作系统就会开始"杀"进程了,会把它觉得不重要的进程给结束掉,那么如果一个进程在向磁盘写入数据的时候被操作系统杀掉了呢?写入的数据就会丢失掉,为了防止这种情况,在进行大量的IO的情况时,进程就会处于深度睡眠状态,来防止数据的丢失,这种情况下,操作系统是不能杀掉这个进程的,只有等待IO结束后自己退出。

这里的S,D状态都叫做阻塞状态,由于某种资源未就绪,有可能是键盘,显示屏,鼠标,也有可能是另一个进程,而导致PCB没有在运行队列中排队,在其他资源的阻塞队列中排队,所以在操作系统中有非常非常多的阻塞队列,当进程等待的资源就绪后就会把自己重新列入到CPU的运行队列里面排队。

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

输入19号信号就可以让进程暂停

发送19号后进程状态变为T

ps:暂停状态属于挂起状态。

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

3.僵尸进程

僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程

僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。

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

代码演示:

cpp 复制代码
  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 void runchild()
  5 {
  6         int cnt=1;
  7         while(cnt)
  8         {
  9                 printf("I am child:%d,ppid:%d\n",getpid(),getppid());
 10                 sleep(1);
 11                 cnt--;
 12         }
 13 }
 14 int main()
 15 {
 16         for(int i=0;i<5;i++)
 17         {
 18                 pid_t id=fork();
 19                 if(id==0)
 20                 {
 21                         runchild();
 22                         exit(0);
 23                 }
 24         }
 25         sleep(15);
 26         return 0;
 27 }

我们使用fork()函数创建5个子进程,让它们执行同一个任务,之后退出,父进程则等待15秒后再退出。

这个时候我们发现子进程的状态都变成了Z+,也就是处于了僵尸状态。

危害:

  1. 资源占用:尽管僵尸进程不再执行任务,但它们仍然占据着PID等资源。如果系统中存在大量僵尸进程,它们会导致进程表的资源被占满,影响系统的正常运行。
  2. 内存泄漏:未及时回收的僵尸进程会导致内存泄漏,增加系统的负担,影响系统稳定性。

处理方法:使用进程wait()函数可以解决,后面会细说。

4. 孤儿进程

父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?

父进程先退出,子进程就称之为"孤儿进程"

孤儿进程被1号init进程领养,当然要有init进程回收喽。

对代码稍作修改,让父进程先退出。

cpp 复制代码
 1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 void runchild()
  5 {
  6         int cnt=5;
  7         while(cnt)
  8         {
  9                 printf("I am child:%d,ppid:%d\n",getpid(),getppid());
 10                 sleep(1);
 11                 cnt--;
 12         }
 13 }
 14 int main()
 15 {
 16         for(int i=0;i<5;i++)
 17         {
 18                 pid_t id=fork();
 19                 if(id==0)
 20                 {
 21                         runchild();
 22                         exit(0);
 23                 }
 24         }
 25         return 0;
 26 }

可以看到子进程的父进程被1号进程收养,变成了1号进程的子进程。

5.孤儿进程和僵尸进程的对比

1. 定义

孤儿进程:父进程先于子进程退出,子进程被 init 进程(PID=1)或 systemd 接管,仍能正常运行。

僵尸进程:子进程先于父进程退出,但父进程未调用 wait() / waitpid() 回收子进程的退出状态,子进程残留PCB(进程控制块)成为僵尸。
2. 产生原因

孤儿进程:父进程由于某种原因先退出,未等待子进程结束。

僵尸进程:父进程未处理子进程的 SIGCHLD 信号,也未主动回收子进程资源。

3. 系统影响

孤儿进程:无负面影响,被 init 接管后,子进程退出时会被 init 自动回收,不会残留资源。

僵尸进程:占用PCB资源(PID、退出状态等),PID是有限资源,大量僵尸进程会导致系统无法创建新进程。

4. 解决方法

孤儿进程:无需特殊处理,系统自动接管回收;也可在父进程中通过信号或等待机制避免父进程提前退出。

僵尸进程:

  1. 父进程主动调用 wait() / waitpid() 阻塞或非阻塞回收子进程;

  2. 父进程注册 SIGCHLD 信号处理函数,在信号回调中回收子进程;

  3. 父进程fork后立即exit,让子进程被 init 接管(避免父进程未回收)。

相关推荐
正在学习前端的---小方同学4 小时前
Harbor部署教程
linux·运维
牛奔4 小时前
Docker Compose 两种安装与使用方式详解(适用于 Docker 19.03 版本)
运维·docker·云原生·容器·eureka
翼龙云_cloud5 小时前
阿里云渠道商:如何手动一键扩缩容ECS实例?
运维·服务器·阿里云·云计算
Sean X5 小时前
Ubuntu24.04安装向日葵
linux·ubuntu
墨风如雪5 小时前
拒绝被找回!MJJ必修课:Outlook邮箱交易后的“防回手”安全设置全攻略
服务器
DX_水位流量监测6 小时前
大坝安全监测之渗流渗压位移监测设备技术解析
大数据·运维·服务器·网络·人工智能·安全
电商API&Tina6 小时前
京东 API 数据采集接口接入与行业分析
运维·服务器·网络·数据库·django·php
IT 乔峰6 小时前
脚本部署MHA集群
linux·shell
dz小伟6 小时前
execve() 系统调用深度解析:从用户空间到内核的完整加载过程
linux
Mr_Xuhhh6 小时前
博客标题:深入理解Shell:从进程控制到自主实现一个微型Shell
linux·运维·服务器