为了更好的去了解进程,本博客来看看进程的每一种状态
1.就绪,阻塞,挂起
一般的操作系统的进程状态可以分为就绪,阻塞,挂起三种状态。
就绪:
资源已经准备就绪,进入就绪队列,随时可以被调度。
阻塞:
进程在阻塞状态下,通常是因为它正在等待某些资源的就绪,比如等待 I/O 操作完成、等待文件锁定、等待网络响应等。进程在这个状态下并不会占用 CPU 时间,而是进入等待队列,直到所需的资源变得可用,才会被操作系统重新调度执行。
阻塞的原因:
- I/O 操作:例如,读取文件、网络请求、硬盘访问等。
- 资源等待:例如,等待 CPU 时间片、内存、锁等。
- 同步等待:例如,等待其他进程的信号或消息。
阻塞状态的表现:
- 阻塞状态的进程不能继续执行,直到它所等待的资源或事件发生。
- 阻塞的进程进入操作系统的等待队列,并不占用 CPU 资源。
- 操作系统根据不同的等待类型(I/O、锁等)将进程排入不同的队列。
例子:
假设你在使用某个应用程序,它需要从网络获取数据。在等待数据从服务器返回之前,进程就会进入阻塞状态。在此期间,它不会占用 CPU,而是等待数据返回。
挂起:
挂起是进程脱离就绪或者阻塞队列,同时不会被CPU调度的一种表现,操作系统挂起进程的核心是优化资源利用率、支持多任务协作或保障系统稳定性。
挂起的原因:
- 用户手动挂起进程:用户通过终端或工具主动暂停进程,方便后续恢复。例:Linux中 Ctrl+Z 发送 SIGTSTP 信号,将前台进程置为停止态(T状态)
- 系统调度优化:内核调度器为提升整体效率,主动挂起低优先级进程 例:采用"抢占式调度"的系统中,高优先级进程就绪时,会挂起当前运行的低优先级进程;CPU负载过高时,挂起部分非核心进程,保障系统服务稳定。
- 调试或异常处理:调试器(如gdb)发送 SIGSTOP 信号,将目标进程置为跟踪态(T状态),方便逐行调试;
挂起状态的表现:
- 脱离CPU调度队列:挂起进程不会出现在CPU就绪队列中,调度器不会分配CPU时间片,
- 上下文完整保留:进程的PCB(进程控制块)、内存空间、寄存器数据、程序计数器等均被保留,唤醒后可无缝恢复执行(如同暂停视频后继续播放)。
- 执行停滞:进程的代码不再推进,不会进行任何运算或I/O操作(除非是挂起前已发起的异步I/O,如磁盘读写,完成后会唤醒进程)。
- 资源占用不变:进程仍占用已申请的内存、文件句柄、网络连接等资源,不会释放(区别于"终止态"),如果内存资源紧张,操作系统会进行内存的换入换出。
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+,也就是处于了僵尸状态。
危害:
- 资源占用:尽管僵尸进程不再执行任务,但它们仍然占据着PID等资源。如果系统中存在大量僵尸进程,它们会导致进程表的资源被占满,影响系统的正常运行。
- 内存泄漏:未及时回收的僵尸进程会导致内存泄漏,增加系统的负担,影响系统稳定性。
处理方法:使用进程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. 解决方法
孤儿进程:无需特殊处理,系统自动接管回收;也可在父进程中通过信号或等待机制避免父进程提前退出。
僵尸进程:
-
父进程主动调用 wait() / waitpid() 阻塞或非阻塞回收子进程;
-
父进程注册 SIGCHLD 信号处理函数,在信号回调中回收子进程;
-
父进程fork后立即exit,让子进程被 init 接管(避免父进程未回收)。