进程的五大状态及特殊进程解析

前言

在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、后台运行等情况进行判断。

守护进程存在的意义,通过意义了解存在目的:

  1. 提供系统常驻服务,系统很多核心服务都以守护进程形式运行的,比如MySQL、日志、Redis等,这些进程不需要人工交互,开机自启动,为后台提供持续性的服务。
  2. 守护进程会创建独立会话与进程组,拥有独立的会话环境,不会受终端信号的影响,稳定性更高,不会被普通操作意外杀死。
  3. 统一被系统管理,由系统进行资源分配回收,方便后期运维,适合做服务器后台服务、守护监控程序。
  4. 普通进程依赖终端,一旦关闭终端或退出用户,进程就会终止。而守护进程脱离了终端,在系统后台运行。

守护进程的创建代码演示:

复制代码
#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 ,代表后台休眠等待进程运行。
相关推荐
生而为虫1 小时前
Claude Code 最新版安装教程(Windows/Mac/Linux 全平台) 面向普通用户的 Claude Code 安装与模型接入指南
linux·windows·macos
吟安安安安1 小时前
适合短期冲刺的学习工作流(针对算法)
学习·算法
科研前沿1 小时前
什么是时空融合技术?
大数据·人工智能·数码相机·算法·重构·空间计算
AI科技星1 小时前
全域数学本源公理:0、1、∞ 三者核心关系 (典籍定稿版)
人工智能·算法·数学建模·数据挖掘·量子计算
AI科技星2 小时前
全域数学·第卷:场计算机卷(场空间计算机)【乖乖数学】
java·开发语言·人工智能·算法·机器学习·数学建模·数据挖掘
Deepoch2 小时前
数学模型驱动:Deepoc 低幻觉数学大模型助力发动机全周期智能优化
人工智能·算法·机器学习·deepoc·数学大模型·低幻觉
嘻嘻哈哈樱桃2 小时前
牛客经典101题解题集--贪心算法+模拟
java·python·算法·贪心算法
AKDreamer_HeXY2 小时前
QOJ 12255 - 36 Puzzle 题解
数据结构·c++·数学·算法·icpc·qoj
Sarvartha2 小时前
三目运算符
linux·服务器·前端