前言
在Linux操作系统中,为了实现CPU资源的高效并发调度、合理分配软硬件资源,系统会对进程的完整生命周期进行阶段划分与状态管控。
进程状态是操作系统调度机制、并发编程、进程资源回收的核心底层基础,也是理解就绪排队、CPU抢占、IO阻塞、进程退出残留等核心场景的关键。在日常的开发与运维时,很多常见问题,比如进程卡顿、CPU占用过高、僵尸进程资源泄露、孤儿进程自动托管等,本质都是进程状态流转异常导致。
本文展示了进程的五大基础状态,并通过代码进行对应状态的效果展示,以及几个特殊进程存在的介绍。
进程状态
进程状态定义:操作系统为了高效调度 CPU 资源,对进程从创建、运行、阻塞到终止整个生命周期划分的不同运行阶段。每个进程任一时刻只会处于一种状态,并会在触发特定事件时发生状态切换。
进程分为五大基础态 :创建态、就绪态、运行态、阻塞(等待)态、终止态。

常见的状态码:
| 进程状态 | 状态码 | 解释 |
|---|---|---|
| 运行态 / 就绪态 | R | 正在 CPU 执行,或在就绪队列排队等着运行 |
| 阻塞 / 等待态(可中断) | S | 休眠、等 IO、等信号,能被信号唤醒 |
| 阻塞 / 等待态(不可中断) | D | 正在磁盘 IO,不能被信号杀死 |
| 暂停 / 挂起态 | T | 被暂停,收到信号才会继续运行 |
| 终止态 | Z | 子进程已退出,父进程没回收,PCB 残留 |
创建态
进程刚被创建时,PCB正在进行初始化,资源都未完全分配,尚未进入就绪队列,不参与CPU的调度过程。
此状态下仅在系统内部进行初始化,进程还不可运行,并且持续时间短,难以查看,用户也无法进行干预。
就绪态
进程已经具备运行条件,只差CPU时间片,只要分配到CPU就能立刻就行运行。
状态标示符为:R
运行态
进程正在CPU上执行代码,单CPU同一时刻只有一个进程能处于运行态。
状态标示符为:R
就绪态与运行态本质没什么不同,区别就是就绪态状态下谁抢到CPU时间片谁就是运行态,所以代码演示用同一个代码。
代码演示:
cpp
#include <iostream>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid = fork();
if(pid < 0)
{
printf("fork error\n");
return -1;
}
else if(pid == 0)
{
printf("I'm child process.pid:%d\n",getpid());
// 子进程空循环,不阻塞、不退出,持续就绪/运行
while(1)
{
;
}
return 0;
}
else
{
// 父进程休眠,维持进程环境
printf("I'm parent process.pid:%d\n",getpid());
sleep(100);
printf("父进程结束\n");
}
return 0;
}
代码解析: 子进程创建完成后,内存、资源全部就绪,无需等待任何外设,仅排队争抢 CPU 时间片。没有抢到时间片时处于纯粹就绪态,抢到瞬间为运行态,二者不断轮换。
下图为代码运行后的进程状态。由于我们给子进程设置了死循环,使其持续占用 CPU 进行运算;父进程则调用sleep函数进入休眠等待。我们打开新终端通过ps aux命令查看进程状态,可以看到:死循环的子进程处于 R(运行 / 就绪)状态 ,执行 sleep 休眠的父进程处于 S(可中断睡眠)状态。


阻塞态
进程等待某事件发生(如IO读写、等待信号、等待子进程等),此状态就算给CPU也无法执行,只有事件完成后才能退出阻塞状态,且只能回到就绪态,不能回到运行态。
状态标示符:S
代码演示:
#include <iostream>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid = fork();
if(pid < 0)
{
printf("fork error\n");
return -1;
}
else if(pid == 0)
{
printf("i am child process.pid:%d\n",getpid());
printf("子进程进入阻塞状态\n");
// 主动放弃CPU,阻塞等待时间事件完成
sleep(10);
printf("子进程阻塞结束\n");
return 0;
}
else
{
sleep(20);
printf("i am parent process.pid:%d\n",getpid());
printf("父进程结束\n");
}
return 0;
}
下图为运行结果:子进程通过调用 sleep() 主动放弃 CPU,进入阻塞队列。此时无论 CPU 是否空闲,进程都无法运行。睡眠时间结束后,进程不会直接运行,先转为就绪态等待调度。


终止态
进程执行完毕、被系统杀死、异常退出等情况时,进程持有的资源会逐步被回收,而进程所持有的PCB不会直接销毁 ,而是保留一段时间,等待父进程完成资源回收并读取查询退出状态后,进程才会被销毁。此状态就为下面要提的僵尸进程。
状态标示符:Z
代码演示:
#include <iostream>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid = fork();
if(pid < 0)
{
printf("fork error\n");
return -1;
}
else if(pid == 0)
{
//子进程退出,进入终止态
printf("i am child process.pid:%d\n",getpid());
printf("子进程结束\n");
exit(0);
}
else
{
// 延迟回收,让用户有时间查看终止僵尸态
sleep(3);
printf("i am parent process.pid:%d\n",getpid());
sleep(100);
printf("父进程结束\n");
}
return 0;
}
运行结果展示:子进程调用exit(0)执行完毕,进入终止态。所有运行资源被内核释放,但父进程未调用 wait 回收,PCB 残留,进程呈现 Z 僵尸状态。父进程退出后,进程自动回收 PCB,进程彻底销毁。


特殊进程
学习完五个进程状态后,我们来了解常见的三个特殊进程,分别为僵尸进程 与孤儿进程 以及守护进程,三者均是父子进程运行时序异常、进程资源回收机制特殊导致的特殊进程。
僵尸进程
当子进程正常退出后 ,内核释放进程的所有资源,但是父进程却不调用 wait / waitpid 来回收子进程的退出状态 ,导致子进程的进程控制块依然保留在操作系统内核进程表中,这种"已经死亡、但内核仍保留信息、等待父进程收尸"的进程称为僵尸进程,用符号Z表示。
运行对应代码但父进程不回收子进程状态:

通过另一个终端查看对应的僵尸进程:

完整演示代码:
cpp
#include <iostream>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid = fork();
if(pid < 0)
{
printf("fork error\n");
return -1;
}
else if(pid == 0)
{
//子进程退出
printf("i am child process.pid:%d\n",getpid());
printf("子进程结束\n");
exit(0);
}
else
{
sleep(3);
//父进程不回收子进程信息
printf("i am parent process.pid:%d\n",getpid());
//给足够时间去查看进程状态
sleep(100);
printf("父进程结束\n");
}
return 0;
}
孤儿进程
孤儿进程 指的是父进程先于子进程退出 ,子进程还在运行,失去了亲生父进程,就被称为孤儿进程 。孤儿进程没有指定符号,父进程退出后,子进程就会被其他进程收养,所以可通过父进程pid判断是否为孤儿进程。(默认由pid为1的进程收养)
下图为程序运行后进入sleep的情况,可以看到父进程pid已经变为了1:

通过另一个终端进行查看,可以看到父进程pid也变成了1:

完整演示代码:
cpp
#include <iostream>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid = fork();
if(pid < 0)
{
printf("fork error\n");
return -1;
}
else if(pid == 0)
{
//子进程打印自身pid以及父进程pid
printf("I am child process,pid:%d , ppid:%d\n",getpid(),getppid());
//休眠一段时间确保父进程有时间退出
sleep(3);
//打印自身pid以及父进程pid
printf("I am child process,pid:%d , ppid:%d\n",getpid(),getppid());
//给足够的时间查看子进程状态
while(1)
{
sleep(1);
}
}
else
{
//父进程不做操作,仅打印自身pid后退出
printf("I am parent process,pid:%d\n",getpid());
exit(0);
}
return 0;
}
守护进程
守护进程指的是运行在后台、脱离终端控制、长期运行的特殊进程。守护进程由于其特性,所以也是一种孤儿进程,由pid为1的进程收养。守护进程本身没有指定符号,可通过无终端、父进程pid为1、后台运行等情况进行判断。
守护进程存在的意义,通过意义了解存在目的:
- 提供系统常驻服务,系统很多核心服务都以守护进程形式运行的,比如MySQL、日志、Redis等,这些进程不需要人工交互,开机自启动,为后台提供持续性的服务。
- 守护进程会创建独立会话与进程组,拥有独立的会话环境,不会受终端信号的影响,稳定性更高,不会被普通操作意外杀死。
- 统一被系统管理,由系统进行资源分配回收,方便后期运维,适合做服务器后台服务、守护监控程序。
- 普通进程依赖终端,一旦关闭终端或退出用户,进程就会终止。而守护进程脱离了终端,在系统后台运行。
守护进程的创建代码演示:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid = fork();
if(pid < 0)
{
printf("fork error\n");
return -1;
}
if(pid > 0)
{
// 父进程退出
exit(0);
}
// 创建新会话,脱离终端
setsid();
// 再次fork,保证无法申请终端
pid = fork();
if(pid > 0)
{
exit(0);
}
// 守护进程开始运行
printf("I am daemon process, pid:%d, ppid:%d\n",getpid(),getppid());
// 永久后台运行
while(1)
{
sleep(1);
}
return 0;
}
注意!!!
守护进程需要手动删除,无法像普通进程那样通过 ctrl + c 进行关闭,通过在终端输入指令来杀死守护进程:
kill 进程PID如果出现问题就强制关闭:
kill -9 进程PID
代码运行结果查询:

通过终端代码进行查看:
ps -ef | head -2 && ps -ef | grep process

守护进程的查看关键:
- TTY列:显示字符 ?,代表该进程脱离终端,是守护进程的核心识别标志。
- ppid列:显示为 1 ,代表被系统父进程进程1收养。
- STAT列:显示为 S ,代表后台休眠等待进程运行。