孤儿进程和僵尸进程的概念及进程回收(wait函数及waitpid函数)

孤儿僵尸进程回收
本文解析了Linux系统中孤儿进程和僵尸进程的概念,介绍了如何通过init进程收养孤儿进程及父进程如何通过wait和waitpid函数回收僵尸进程资源。

一.孤儿进程
孤儿进程可以理解为一个子进程的父进程英年早逝(父进程先于子进程退出),就将这样的一个进程称为孤儿进程,在linux操作系统上。孤儿进程被init进程收养,此时孤儿进程的ppid==1,即init进程的pid == 1。也就是说init进程变成孤儿进程的父进程(干爹)。

下面举例说明什么是孤儿进程:

#include<unistd.h>
#include<sys/types.h>
#include<stdio.h>

int main()
{
    pid_t pid = fork();
    if(pid == 0)//子进程
    {
        sleep(1);//目的是让父进程先于子进程执行完毕
        printf("son process start\n");
        printf("my pid is %d,my ppid is %d\n",getpid(),getppid());
        printf("son process end\n");
    }
    else
    {
        printf("father process start\n");
        printf("my pid is %d\n",getpid());  
        printf("father process end\n");
    }
    return  0;
}

这里写图片描述
从执行结果来看,此时由pid == 3363父进程创建的子进程,其输出的父进程pid == 1,说明当其为孤儿进程时被init进程回收。

我们查证一下init进程的pid是否为1
这里写图片描述

操作系统为什么要给孤儿进程分配init进程收养孤儿进程?
其目的只有一个,就是为了释放系统资源
进程结束之后,能够释放用户区空间。但不能释放pcb(进程控制块),即内核资源。pcb必须由子进程的父进程进行释放。

二.僵尸进程
(1)父进程成功创建子进程,且子进程先于父进程退出。
(2)子进程需要父进程回收其所占资源,释放pcb。但是父进程不作为,不去释放已经退出子进程的pcb。
(3)这样的子进程变为僵尸进程。
(4)僵尸进程是一个已经死掉了的进程。

下面举例验证什么是僵尸进程:

#include<unistd.h>
#include<sys/types.h>

int main()
{
    pid_t pid = fork();//创建子进程
    if(pid == 0)
    {
        printf("my pid is %d\n",getpid());
        printf("my ppid is %d\n",getppid());
    }
    else
    {
        while(1)
        {
            ;//死循环,阻塞  不处理已经退出的子进程,致使子进程称为僵尸进程
        }
    }
    return 0;
}

这里写图片描述

从图可以看出,子进程执行完毕。父进程处于阻塞状态(陷入死循环)。
再开启另外一个bash窗口,使用ps -aux | grep 3749命令查看僵尸进程的状态。
这里写图片描述
defunct是已死的,僵尸的意思。
也就是说pid值为3749的进程为僵尸进程,是因为其父亲不对已经结束的子进程做进程回收。但是已经结束的子进程的资源必须由其父进程进行回收,因而产生了僵尸进程。

试想一下,如果有大量的僵尸进程驻在系统之中,必然消耗大量的系统资源。但是系统资源是有限的,因此当僵尸进程达到一定数目时,系统因缺乏资源而导致奔溃。所以在实际编程中,避免和防范僵尸进程的产生显得尤为重要。

三.进程回收
(1)回收僵尸进程的资源,一种比较暴力的做法是将其父进程杀死,那么子进程资源也将被回收。但是这种做法在大多数情况下都是不可取的,如父进程是一个服务器程序,如果为了回收其子进程的资源,而杀死服务器程序,那么将导致整个服务器崩溃,得不偿失。显然这种回收进程的方式是不可取的,但其也有一定的存在意义。那么有没有更好的解决方案呢,且看下边的两种方式。
注:kipp -9 + (父进程的pid)是第一种回收子进程资源的方式。

(2)wait系统调用函数
所需头文件:

#include<sys/types.h>
#include<sys/wait.h>

函数原型: pid_t wait(int* status),是一个阻塞函数。

返回值:如果为-1,回收失败,已经没有子进程可以回收了。
如果 > 0,返回值为子进程对应的pid。

参数:子进程的退出状态,是一个传出参数。
判断子进程是如何死的 (1)正常退出 (2)被信号杀死

(1)WIFEXITED(status):为非0,进程正常结束。
WEXITSTATUS(status):如果上宏为真,使用此宏,获取进程的退出状态(exit/return的参数)。
(2)WIFEXITEDWIFSIGNALED(status):为非0,进程异常终止。
WTERMSIG(status):如上宏为真,使用此宏,获取使进程终止的那个信号的编号。

注意:pid_t类型即为int类型。调用一次,只能回收一个子进程,如果回收多个子进程,就需要多次调用wait函数。
函数功能:
(1)阻塞并等待子进程退出。
(2)回收子进程残留资源。
(3)获取子进程结束状态(退出原因)。

实例一:不关心子进程的退出状态

#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    pid_t pid = fork();
    if(0 == pid)
    {
        printf("my pid is %d\n",getpid());
    }
    else
    {
        sleep(1);//目的是让子进程先于父进程结束
        /*wait函数是阻塞函数,会等到子进程结束回收资源*/
        pid_t dpid = wait(NULL);//对子进程的退出状态不关心
        printf("the died son process pid is %d\n",dpid);
    }
    return 0;
}

执行结果:
这里写图片描述

实例二:利用参数int* status获取子进程的退出状态

1.获取正常退出的状态

#include<sys/types/h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdio.h>

int main()
{
    pid_t pid = fork();
    if(0 == pid)
    {
        printf("my pid is %d\n",getpid());
    }
    else
    {
        sleep(1);
        int status = 0;//初始化为0
        pid_t dpid = wait(&status);
        if(WIFEXITED(status))
        {
            printf("exit value:%d\n",WEXITSTATUS(status));
        }
        printf("the died son process pid is %d\n",dpid);
    }
    return 10;//返回值为10  
}

执行结果:
这里写图片描述
2.获取异常退出的状态。

#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdio.h>

int main()
{
    pid_t pd = fork();
    if(pid == 0)
    {
        while(1)
        {
            printf("my pid is %d\n",getpid());
            sleep(2);//控制执行速度不要太快
        }
        else
        {
            sleep(1);
            int status = 0;//初始化为0
            wait(&status);
            if(WIFSIGNALED(status))
            {
                printf("exit by signal : %d\n",WTEEMSIG(status));
            }
        }
    }
    return 0;
}

这里写图片描述
查找子进程的pid值,并杀死。
这里写图片描述

再次查看执行结果
这里写图片描述

使用kill -l命令可以擦看所有信号,其中编号为9的信号是SIGKILL
这里写图片描述

子进程被编号为9的信号杀死。
(3)waitpid系统调用函数
函数功能:和wait函数相同。
所需头文件

#include<sys/types.h>
#include<sys/wait.h>

函数原型: pid_t waitpid(pid_t pid,int* status,int options)
返回值:
(1)>0:返回清理掉的子进程的PID。
(2)-1:无子进程可以回收。
(3)=0:参数3为WNOHANG,且子进程正在运行。
参数:
1.pid 可以有选择的回收
(1)pid:pid == -1,等待任意子进程,与wait等效。
(2)pid > 0,等待其进程ID与pid相等的子进程。
(3)pid == 0,等待其组ID等于调用进程的组ID的任一子进程。
(4)pid < -1,等待其组ID等于pid的绝对值的任一子进程。

2.status:子进程的退出状态,用法同wait函数。
3.options:设置为WNOHANG,函数非阻塞。设置为0,阻塞函数。

<think> 首先,用户的问题是:"我想查找避免孤儿进程僵尸进程的方法 请问避免孤儿进程僵尸进程的方法"。我需要根据提供的引用系统级指令来回答。 从引用中: - 引用[1]提到孤儿进程由init进程管理,无害。 - 引用[2]解释了僵尸进程的危害如何避免:父进程需要回收进程的资源。 - 引用[3]给出了僵尸进程的定义避免方法:父进程调用waitwaitpid获取子进程状态。 关键点: - **孤儿进程**:当父进程退出时,子进程成为孤儿,被init进程接管。init进程会自动回收这些子进程,所以孤儿进程本身无害。 - **僵尸进程**:子进程退出后,父进程没有调用wait/waitpid回收它,导致僵尸进程累积,占用系统资源。 用户问的是避免孤儿进程僵尸进程的方法: - 孤儿进程:既然无害,不需要特别避免。关键是确保系统正常运行。 - 僵尸进程:需要避免,方法是确保父进程回收进程。 避免僵尸进程的方法: 1. 父进程调用wait()waitpid()系统调用来回收进程。 2. 使用信号处理机制,例如在父进程中忽略SIGCHLD信号(但这不是最佳实践)。 3. 在父进程中设置异步处理。 4. 使用fork()后,父进程等待子进程结束。 参考引用: - [^2]: "父进程没有对其进行善后处理(获取终止子进程的有关信息、释放它仍占用的资源)进程被称作僵尸进程",避免方法是回收资源。 - [^3]: "避免僵尸进程的方法:父进程调用waitwaitpid获取子进程的状态信息。" 对于孤儿进程,引用[1]说:"因为init进程会主动收集所有子进程的退出状态信息,所有由init进程管理的子进程是不会变成僵尸进程",所以孤儿进程无害,不需要避免。 因此,回答应聚焦于避免僵尸进程的方法。 系统级指令: - 使用$...$ for inline math (not needed here, as this is about processes, not math). - Independent formulas use $$...$$ (not applicable). - LaTeX syntax correct (not needed in code). - Use Chinese to answer. - Generate related questions. - Add reference identifiers naturally at the end of cited paragraphs. 回答结构: - 根据用户问题,清晰结构化回答。 - 先解释孤儿进程僵尸进程的区别。 - 然后给出避免方法。 - 引用引用部分。 相关引用段落: - From [^1]: 孤儿进程无害。 - From [^2]: 僵尸进程的危害避免方法。 - From [^3]: 定义避免僵尸进程。 在回答中,自然地添加引用标识,例如在解释后加[^1], [^2], [^3]。 最后,生成相关问题。 完整回答结构: 1. **介绍**:简述孤儿进程僵尸进程。 2. **避免孤儿进程**:解释为什么不需要避免,因为无害。 3. **避免僵尸进程**:详细给出方法。 4. **总结**。 5. **生成相关问题**。 </think> ### 避免孤儿进程僵尸进程的方法 在操作系统(如Linux)中,孤儿进程僵尸进程是常见的进程管理问题。以下基于引用内容,我将逐步解释避免方法。孤儿进程通常无害且无需特意避免,而僵尸进程需要主动处理以防止资源泄露[^1][^3]。 #### 1. **孤儿进程及其避免方法** - **定义**:当父进程意外退出时,子进程成为孤儿进程。这些进程会被init进程(PID为1)自动接管。init进程回收进程的资源,因此孤儿进程不会累积或危害系统[^1][^3]。 - **避免方法**:由于孤儿进程无害且由系统管理,**无需特殊避免**。但可以通过代码设计减少其发生概率: - 确保父进程在退出前正确关闭所有子进程。 - 使用进程组(如`setpgid`),避免父进程意外终止。 - 示例:在Python中,父进程可通过`os._exit(0)`退出前清理子进程。 引用[^1]强调:"因为init进程会主动收集所有子进程的退出状态信息,所有由init进程管理的子进程是不会变成僵尸进程。" 因此,孤儿进程是安全的[^1]。 #### 2. **僵尸进程及其避免方法** - **定义**:子进程结束后,父进程未调用`wait()`或`waitpid()`回收其资源(如进程控制块PCB),导致僵尸进程累积占用PID资源,可能影响系统性能[^2][^3]。 - **避免方法**:僵尸进程必须主动避免,以下是核心策略: - **调用`wait()`或`waitpid()`系统调用**:父进程在创建子进程后,必须使用这些函数获取子进程终止状态,释放资源。这是最可靠的方法。 - 在C中使用:`pid_t waitpid(pid_t pid, int *status, int options);` - 在Python中使用`os.wait()`或`os.waitpid()`。 - 示例代码(Python): ```python import os import time pid = os.fork() if pid == 0: # 子进程 print("子进程运行中...") time.sleep(2) os._exit(0) # 子进程退出 else: # 父进程 os.waitpid(pid, 0) # 父进程等待并回收进程 print("父进程回收进程完成") ``` 此代码确保父进程调用`waitpid()`后,子进程资源被释放,避免僵尸化[^2][^3]。 - **设置信号处理函数**:父进程捕获`SIGCHLD`信号(当子进程退出时触发),在信号处理函数中调用`wait()`。 - 示例:在C中使用`signal(SIGCHLD, handler);`,或在Python中使用`signal.signal(signal.SIGCHLD, handler)`。 - 注意:此方法需谨慎,避免信号丢失问题[^2][^3]。 - **使用双fork技巧**:创建子进程后,让子进程fork一个孙进程并退出,孙进程由init接管。这样原始父进程可直接调用`wait()`回收进程,避免僵尸。 - 示例代码(简化逻辑): ```c pid_t pid = fork(); if (pid == 0) { // 子进程 if (fork() == 0) { // 孙进程 // 孙进程逻辑 } exit(0); // 子进程退出 } waitpid(pid, NULL, 0); // 父进程回收进程 ``` - **避免父进程忽略子进程**:始终确保父进程在代码中处理子进程退出状态,不要无限期运行而不调用`wait()`。 引用[^2]指出:"僵尸进程是对系统有害的,因为僵尸进程已经挂掉了,对系统没有什么作用,但是依然在进程表占了位置",因此回收资源是核心[^2]。引用[^3]强调:"避免僵尸进程的方法:父进程调用 waitwaitpid 获取子进程的状态信息。"[^3] #### 3. **总结** - 孤儿进程:无需避免,系统自动处理。 - 僵尸进程:必须避免,关键是通过父进程调用`wait()`或`waitpid()`回收资源[^2][^3]。 - 最佳实践:在编写多进程程序时,始终在父进程添加回收逻辑;测试时使用`ps aux | grep defunct`检查僵尸进程
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值