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 接管(避免父进程未回收)。

相关推荐
测试者家园2 小时前
DevOps 到底改变了测试什么?
运维·自动化测试·软件测试·devops·持续测试·智能化测试·软件测试和开发
扛枪的书生2 小时前
Linux 通用软件包 AppImage 打包详解
linux
只想安静的写会代码3 小时前
网卡信息查询、配置、常见故障排查
linux·服务器·windows
jiayong233 小时前
多子系统架构下的Nginx部署策略与最佳实践
运维·nginx·系统架构
皮糖小王子3 小时前
Docker打开本地镜像
运维·docker·容器
wavemap3 小时前
阿里云38元一年200M轻量云服务器详细评测
服务器·阿里云·云计算·vps·评测·boboforum
偶像你挑的噻5 小时前
9-Linux驱动开发-设备树=>设备树插件实现 RGB 灯驱动
linux·驱动开发·stm32·嵌入式硬件
叫致寒吧5 小时前
Nginx基于域名的虚拟主机实操案例
运维·服务器·nginx
施努卡机器视觉6 小时前
SNK施努卡车门自动化安装
运维·自动化