Linux进程状态详解:僵尸进程与孤儿进程的深度探索与实践

文章目录


前言

在Linux操作系统中,进程状态是操作系统资源调度和管理的重要组成部分。每个进程在其生命周期中都会经历多种状态,常见的有运行、睡眠、停止等,而其中的 僵尸进程 和 孤儿进程 则是我们必须特别关注的特殊进程类型。僵尸进程虽然已终止,但由于父进程未回收其退出状态,依然占据系统资源;而孤儿进程则是在父进程结束后继续存在,并由 init 进程接管。这些特殊进程的存在可能会对系统的资源管理和性能产生影响,了解它们的形成原因、特点及解决方式,对于优化系统、提高效率具有重要意义。本文将深入解析Linux中的进程状态,重点讨论僵尸进程与孤儿进程的形成、影响及处理方法。


一、进程状态概述

操作系统中的进程状态反映了每个进程当前的活动状态。以下是 常见的进程状态,包括其意义、特点及常见场景。

1.1 运行状态

描述: 进程正在 CPU 上执行,或者处于就绪状态等待 CPU 调度。

触发

  • 进程被调度到 CPU 上运行。
  • 睡眠态的进程被唤醒并准备运行。

详解 :一般的计算机只有一个CPU,但是进程却是有很多个,对 CPU 来说,运行的本质就是被 CPU 调度,所以每个 CPU 都会有一个运行队列,所有的进程要运行都必须在运行队列上排队,而参与排队的是每一个进程对应的PCB 对象

一个进程被 CPU 调度运行,并不是要一直运行直到完毕,假如说一个程序里有一个死循环,导致程序无法终止,进程没法结束,那这时其他的进程就没办法运行,操作系统为了避免一个进程长时间占用 CPU 资源的情况的发生,提出了一个叫做时间片的概念。

时间片(Time Slice)是操作系统中用于实现进程调度的一种机制。在多任务操作系统中,CPU的时间被划分为若干小段,每个进程在一个时间片内运行,时间片用完后,操作系统会暂停该进程的执行,将CPU分配给下一个进程。这种方式是实现多进程并发执行的关键。

时间片的长度对系统性能有很大影响:

  • 时间片过短会导致频繁的上下文切换,增加系统开销。
  • 时间片过长则会导致响应时间较长,影响系统的实时性。

时间片的调度通常由操作系统内核根据不同的调度算法(如轮转法、优先级调度等)来进行管理。

1.2 阻塞状态

阻塞状态(Blocked State)是操作系统中进程的一种状态,指的是进程由于等待某些事件或资源而无法继续执行的状态。常见的情况包括:

  1. 等待输入/输出(I/O)操作完成:当一个进程发起I/O操作(如读取文件、等待网络数据)时,如果I/O操作还没有完成,进程会进入阻塞状态,直到I/O操作完成。
  2. 等待信号量或资源:进程可能需要等待某个资源(如共享内存、锁等)或同步信号(如信号量、条件变量等),如果该资源当前不可用,进程将进入阻塞状态。
  3. 等待事件发生:某些进程可能需要等待特定事件的发生才能继续执行,例如等待其他进程的结果或信号。

在阻塞状态下,进程不会占用CPU资源,操作系统会调度其他进程进行执行。一旦进程等待的条件满足,操作系统会将该进程从阻塞状态转换回就绪状态,进程就能继续执行。

举个栗子:

在日常编程中,最常见的一种阻塞情况就是一个进程需要通过键盘读取数据,键盘是一种硬件,在冯诺依曼结构体系中属于输入设备,操作系统对硬件资源的管理是先描述再组织,因此每一个硬件都会对应一个结构体对象,该结构体对象中一定会维护一个等待队列,当一个进程需要利用该硬件资源时,进程的 PCB 对象就会被链入该等待队列,此时进程就处于阻塞状态。

1.3 挂起状态

在操作系统中,挂起状态(Suspended State)通常是指某个进程或任务被暂停执行,但并未完全终止。挂起状态允许操作系统在适当的时候恢复该进程的执行。挂起状态的主要目的是为了更高效地管理系统资源,尤其是在多任务处理和进程调度中。

一个进程在没有被 CPU 调度的情况下,其代码和数据都是空闲的,当系统内存空间告急时,操作系统就会把这些没有被 CPU 调度的进程的数据和代码先放到磁盘中存储,只剩下其对应的 PCB 对象在队列中排队,这种状态就叫做进程的挂起状态。

以上介绍的都是操作系统学科的常见知识,不同的操作系统会有不同的实现方案,现在我们就来看看 Linux 操作系统下的各种进程状态。

二、具体的Linux操作系统中的进程状态

以下是 Linux 操作系统下的常见状态:

状态符号 状态名称 描述
R 运行(Running) 进程正在 CPU 上运行或处于可运行状态,等待被调度。
S 可中断睡眠(Sleeping) 进程正在等待某个事件(如 IO 操作完成),可以通过信号唤醒。
D 不可中断睡眠(Disk Sleep) 进程正在等待硬件资源(如磁盘 IO),无法被信号中断。
T 停止(Stopped) 进程已暂停,通常由用户发送 SIGSTOPSIGTSTP 信号。
Z 僵尸(Zombie) 进程已终止,但其退出状态尚未被父进程读取。
t 挂起(Tracing stop) 进程被调试器(如 gdb)挂起。
X 死亡(Dead) 进程已经终止(极少见)。

2.1 Linux内核源代码

cpp 复制代码
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
	"R (running)", /* 0 */
	"S (sleeping)", /* 1 */
	"D (disk sleep)", /* 2 */
	"T (stopped)", /* 4 */
	"t (tracing stop)", /* 8 */
	"X (dead)", /* 16 */
	"Z (zombie)", /* 32 */
};

2.2 查看进程状态

  • 先来看看下面这段代码执行起来后的进程状态。
c 复制代码
int main()                                                    
{                                                             
    while(1)                                                  
    {    
         printf("Hello \n!");                                                     
    }    
    return 0;    
}  

目前这段代码执行出来的进程状态是 S+ 睡眠状态('+' 代表前台运行,暂时不用管),在这段代码中,printf 的执行涉及到终端输出的 I/O 操作。如果在输出期间终端忙碌或缓冲区满,操作系统会将进程暂时挂起,进入 可中断睡眠态(S)

  • 将 while 循环中的打印去掉再去执行代码看看进程状态。
c 复制代码
int main()                                                    
{                                                             
    while(1);                             
    return 0;    
}  

这段代码执行出来的进程状态是 R+ 运行状态,该程序会在 while(1); 无限循环中不断运行,占用 CPU,但没有任何实际的操作或输出。由于没有被阻塞、暂停或终止,它始终处于 运行状态(Running),并且在没有外部干预的情况下无法退出。

2.3 D磁盘休眠状态(Disk sleep)

有这样一个场景,一个进程需要向磁盘中写入大量数据。在正常情况下,当进程往磁盘写数据时,它需要等待磁盘写入完成,才能继续执行。这时,进程会处于阻塞状态,也就是在内存中等待外部事件(磁盘操作)完成后,再继续执行。

有一天,进程A就在向磁盘写入大量数据,磁盘正在处理这个请求,进D状态的定义:

当进程进入D状态时,它会处于一种深度睡眠状态,无法响应任何外部的信号,甚至不能被操作系统kill掉。这个状态通常是在进程进行I/O操作时,等待磁盘、网络或其他外部资源的响应。当进程处于D状态时,它实际上是在等待一个长时间的I/O操作(如磁盘写入、读取等)。在此期间,其他任何进程无法干扰它的执行,确保了数据的完整性。程A却在内存中翘着二郎腿,嗑着瓜子在等待磁盘写完后通知它。这时,操作系统发现了进程A,系统检查到内存资源非常紧张,认为进程A占用了大量内存,却什么都不做,导致系统性能下降。

于是,操作系统决定将进程 A kill 掉,释放资源。操作系统向进程A发送了kill 信号。可是,进程A并没有响应,因为它此时处于D状态(深度睡眠状态),在等待磁盘写入完成。这个状态下,进程无法响应任何外部信号,也无法被终止。于是,进程A就被强行终止,结果是磁盘写入过程被中断,数据没有被完整写入磁盘。

磁盘一时也无法应对这种情况,它原本正等待进程A的写入操作完成,但由于进程被终止,磁盘只能丢弃那部分未完成的数据。最终,数据没有被保存,文件操作失败。

此时,法官出来审问:

  • 操作系统:"我只是尽力为系统释放内存资源,确保其他进程能顺利运行。我发现进程A占用了大量内存而没有活动,所以就决定将它kill掉。系统流畅性是我的责任,不能因为它占用内存不干正事就放任它。"
  • 磁盘:"我一直按照我该有的规则工作,进程A让我写入数据,结果它在写入中途突然消失。没有进程的指令,数据我只能丢掉。我们磁盘一直是这样的做法,其他磁盘也是如此。"
  • 进程A:"法官,我才是受害者呀!我只是在等待磁盘完成任务,结果就被强行kill掉了,哪里有我的错?"

经过审问,法官得出结论:操作系统、磁盘和进程A都没有错。这场悲剧的发生是因为操作系统设计上的一个问题。法官最终判定:为了避免类似情况发生,必须让进程在向磁盘写入数据时不能被随意终止。因此,**D状态(深度睡眠状态)**应运而生。

D状态的定义:

当进程进入D状态时,它会处于一种深度睡眠状态,无法响应任何外部的信号,甚至不能被操作系统kill掉。这个状态通常是在进程进行I/O操作时,等待磁盘、网络或其他外部资源的响应。当进程处于D状态时,它实际上是在等待一个长时间的I/O操作(如磁盘写入、读取等)。在此期间,其他任何进程无法干扰它的执行,确保了数据的完整性。

2.4 T停止状态(stopped)

在Linux操作系统中,**停止状态(stopped)**是进程的一种状态,通常表示进程被挂起,等待某些条件或操作。与"运行状态"和"睡眠状态"不同,进程处于停止状态时,它已经被显式地暂停执行。

停止状态的概述:
  • **停止状态(stopped)**指的是进程被暂停,通常是通过发送信号或者由操作系统控制某个进程的运行。这种状态的进程不会继续执行,直到收到适当的信号来恢复运行。
停止状态的触发条件:

进程进入停止状态通常由以下原因触发:

  1. 用户发送信号

    • SIGSTOP :这是一个可以让进程进入停止状态的信号,通常是由系统管理员或用户主动发送。使用 kill 命令可以发送此信号,比如:

      bash 复制代码
      kill -STOP <进程ID>

      或者使用更简洁的形式:

      bash 复制代码
      kill -19 <进程ID>  

      19 是SIGSTOP 的信号编号,当进程接收到 SIGSTOP 信号时,它将立即进入停止状态,并停止执行。

    • Ctrl+Z :在命令行界面中,用户可以通过按下 Ctrl+Z 来将当前进程暂停。这实际上就是发送了 SIGSTOP 信号,令进程进入停止状态。

  2. 调试器控制

    • 调试器(如 gdb) :当你使用调试工具进行调试时,调试器会将目标进程暂停,以便开发者检查和修改程序的状态。调试器会发送 SIGSTOP 信号,进程因此进入停止状态。
  3. 父进程发送信号

    • 父进程有时会主动暂停子进程,特别是在进程管理、协调或者资源控制中。可以通过发送 SIGSTOP 或者使用如 waitpid() 的系统调用来暂停进程。
停止状态的行为:
  • 暂停执行:当进程处于停止状态时,它不再消耗CPU资源,不会执行任何指令。它就像是被"冻结"了一样,直到它收到继续执行的信号。

  • 可以恢复 :停止状态的进程并不是永久停止的,它可以通过发送 SIGCONT 信号恢复运行。比如,如果你想恢复一个被暂停的进程,可以使用以下命令:

    bash 复制代码
    kill -CONT <进程ID>

    或者使用更简洁的形式:

    bash 复制代码
    kill -18 <进程ID>

    这会使进程从停止状态恢复到运行状态。

三、僵尸进程

僵尸进程 (Zombie Process)是指已经完成执行的进程,但它的父进程尚未调用 wait() 系统调用来读取它的退出状态,从而没有将它从进程表中清除掉的进程。换句话说,虽然这些进程已经终止,但它们依然在进程表中占据一个条目,等待父进程来回收它们。

3.1 僵尸进程的形成原因:

每个进程在结束时,操作系统会为它保留一定的资源,以便父进程获取子进程的退出状态。如果父进程没有及时通过 wait()waitpid() 来获取这个退出状态,操作系统会将该子进程的状态保留下来,形成一个僵尸进程。

3.2 僵尸进程的特点:

  • 状态为"Z" :在 pstop 命令的输出中,僵尸进程的状态通常显示为 Z(Zombie)。

    cpp 复制代码
    int main()    
    {    
        pid_t id = fork();    
        if(id == 0)    
        {    
            int cnt = 5;    
            while(cnt)    
            {    
                printf("I am child, pid: %d,ppid: %d\n",getpid(),getppid(),cnt);                    
                sleep(1);    
                cnt--;    
            }    
           _exit(0);    
        }    
        else 
        {
            while(1)
            {
                printf("I am father, pid: %d,ppid: %d\n",getpid(),getppid());
                sleep(1);
            }
        }
        return 0;
    }

    上面这段代码在 myproc 进程中通过调用 fork 接口创建了一个子进程,子进程在执行完五次打印后就会被终止掉,其中的 exit 函数就是用来终止一个进程,父进程将一直运行。

  • 占用进程表项 :虽然已经结束执行,但它仍占据一个进程表中的位置。操作系统不会立即回收这些资源,直到父进程调用 wait() 来收集子进程的退出信息。

  • 进程号和父进程号:每个僵尸进程都有一个进程ID(PID),但是它不再占用 CPU 时间。它依然保持父进程ID(PPID),直到父进程回收它。

3.3 如何避免僵尸进程:

  • 父进程调用 wait() :这是最常见的清除僵尸进程的方式。父进程需要在子进程结束时调用 wait() 来获取子进程的退出状态,从而让操作系统回收相关资源。
  • 父进程退出时 :如果父进程退出,而没有处理子进程的退出状态,操作系统会将这些孤儿进程的父进程指向 init 进程(PID 1)。init 进程会定期清理这些孤儿进程,防止它们成为僵尸进程。

四、孤儿进程

孤儿进程 (Orphan Process)是指其父进程已经终止或退出,但该进程本身依然在运行的进程。孤儿进程并不会因为父进程的死亡而被立刻销毁,它会被操作系统自动收养,由系统中的另一个进程接管,通常是 init 进程(PID 1)。

4.1 孤儿进程的形成原因:

孤儿进程的形成是因为进程的父进程在子进程仍在运行时结束。例如,父进程可能因某些原因(如崩溃、被杀死、退出)而提前终止,这时子进程还在继续运行,就成为了孤儿进程。

4.2 孤儿进程的处理:

操作系统会将孤儿进程的父进程ID(PPID)更改为 init 进程(PID 1)。init 进程会定期检查并回收所有孤儿进程的退出状态,确保它们能被清理,避免成为僵尸进程。

  • init 进程的作用init 是系统中的第一个进程,它负责管理系统的启动和关闭。如果一个进程成为孤儿进程,init 会接管并负责清理该进程。它通过调用 wait() 系统调用获取孤儿进程的退出状态,防止它们成为僵尸进程。

4.3 孤儿进程的特点:

  • 父进程死亡:孤儿进程的父进程在它还在运行时已经退出或崩溃。
  • PID变更 :当一个进程变成孤儿进程时,它的父进程ID(PPID)会被更新为 init 进程的PID(通常是 1)。
  • 仍然运行:尽管父进程已结束,孤儿进程通常会继续运行,直到它完成任务或退出。

4.4 孤儿进程与僵尸进程的区别:

  • 孤儿进程 :父进程已经结束,子进程仍在运行。孤儿进程最终会被 init 进程收养并清理。
  • 僵尸进程:子进程已经结束,但其父进程没有及时回收子进程的退出状态,导致进程表中保留了该进程的条目,直到父进程清理该退出状态。

结语

理解Linux中的进程状态,特别是僵尸进程与孤儿进程的处理,不仅有助于提升你对操作系统内部机制的理解,也为实际工作中的系统管理和性能优化提供了有力支持。虽然僵尸进程和孤儿进程看似对系统运行影响不大,但它们的存在和处理方式往往反映了系统资源的有效管理和调度。在实际的开发与运维过程中,掌握如何发现、避免和清理这些进程,能有效提升系统的稳定性和响应速度。希望本文对Linux进程状态的详细分析,能够帮助你更好地理解Linux系统的运行原理,为后续的调优和故障排查提供实用的指导。

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连 支持一下,17的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是17前进的动力!

相关推荐
猎人everest6 分钟前
机器学习之浅层神经网络
人工智能·神经网络·机器学习
一瓢一瓢的饮 alanchan12 分钟前
通过AI自动生成springboot的CRUD以及单元测试与压力测试源码(一)
人工智能·spring boot·单元测试·压力测试·jpa·aicoder·java crud
沈二到不行20 分钟前
深入浅出 Transformers:自注意力和多头注意力的那些事儿
人工智能
aircrushin21 分钟前
【PromptCoder + Trae 最新版】三分钟复刻 Spotify 页面
前端·人工智能·后端
Phodal23 分钟前
成为超级个体:AI 时代研发人员的编程技巧与最佳实践
人工智能·程序员·ai 编程
Matrix7027 分钟前
Apache Spark_解决生产环境数据倾斜问题方案及思路
大数据·分布式·spark
不羁。。35 分钟前
【操作系统安全】任务6:Linux 系统文件与文件系统安全 学习指南
linux·运维·服务器
Phodal36 分钟前
AI 辅助研发的 2024 年的 6 个实践感受与思考
人工智能·ai 编程
唐天下文化1 小时前
第一中标人!晶科能源入围大唐集团19.5GW光伏组件集采
人工智能·5g·能源