Linux孤儿进程和僵尸进程详解(wait和watipid)

 

       当一个进程使用了fork函数会创建一个新的子进程,那么就会存在两个问题,一个是子进程没有结束但是父进程结束了,另一个是子进程结束了但是父进程没有回收子进程的资源。这两种情况就产生了孤儿进程和僵尸进程。下面会通过实际进程运行的示例来进行说明。首先先来明确一个知识点,在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。

孤儿进程

       当子进程还没有结束的时候,父进程先结束了,那么此时的子进程就叫做孤儿进程(毕竟没有了爸爸),那么好在系统中也有福利院,此时系统中的1号进程init就相当于福利院收养了这个子进程,下面我们可以通过ps ajx命令看一下init进程,然后会通过一个代码示例来观察一下子进程是不是被1号进程收养了。

       然后我们看到了这个init,然后我们通过下面的代码来验证一下孤儿进程是不是会被init收养,也就是查看孤儿进程的PPID是否是1就好了,这里用桌面版的Ubuntu和服务器版的Ubuntu的运行结果不同,如果用比较新(旧版的应该没有问题)的桌面版的Ubuntu会发现孤儿进程的PPID并不是1,那么为什么图形化的Ubuntu的孤儿进程没有被init收养可以看下这篇博客:传送门,那么这里我就用服务器版的Ubuntu进行演示,代码如下:

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

int main(void)
{
        pid_t pid = fork();
        if(pid > 0){         // 父进程输出pid并睡眠2s退出
                printf("parent pid is %d\n", getpid());
                sleep(2);
        }
        else if(pid == 0){   // 子进程循环打印pid和ppid
                for(int i=0;i<10;i++){
                        printf("my pid is %d, my ppid is %d\n", getpid(), getppid());
                        sleep(1);
                }
        }
        else {
                perror("fork");
                exit(1);
        }
        return 0;
}

       运行的结果如下图所示:

                                        

僵尸进程

       任何一个子进程在结束后,并不是马上消失掉,而实留下一些资源等待父进程处理,那么僵尸进程就是当子进程比父进程先结束,而父进程又没有释放子进程占用的资源,此时子进程将成为一个僵尸进程。可以通过下面的代码来看一下僵尸进程,代码如下:

/*
    我们让父进程一直循环,子进程打印出pid和ppid后就退出
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>

int main(void)
{
	pid_t pid = fork();
	if(pid > 0){
		while(1){
			printf("parent pid is %d\n", getpid());
			sleep(2);
		}
	}
	else if(pid == 0){
		printf("my pid is %d, my parent pid is %d\n", getpid(), getppid());
		sleep(1);
	}
	else {
		perror("fork");
		exit(1);
	}
	return 0;
}

       我们运行这个程序后,再打开一个终端使用ps ajx命令来查来一下,最终结果如下图所示:

                                            

                   

       我们可以发现子进程退出后,但是它的pid仍然存在,而且状态为Z+,那么Z就是Zombie的意思,说明此时该进程就已经是一个僵尸进程了。僵尸进程的危害:可想而知僵尸进程会造成一定的资源浪费,占用不必要的资源,还有就是当你的进程id达到了最大值的时候,因为有僵尸进程的存在,占用了部分进程id,使得无法再打开新的进程。

       那么为什么系统要让子进程结束的时候等待父进程来处理其资源,而不是直接结束呢?因为父进程有时候需要获取到子进程的退出状态,如果是正常退出,可以直接将其释放,如果是异常退出,又可以根据异常信息进行进一步的相关操作。

wait函数和waitpid函数

       为了解决僵尸进程的问题,可以使用wait函数和waitpid函数来处理,我们先看一下这两个函数的原型:

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

       pid_t wait(int *wstatus);

       pid_t waitpid(pid_t pid, int *wstatus, int options);

       对于wait函数,父进程调用该函数的时候,如果子进程还没有运行结束,那么父进程就会阻塞在这里,直到有子进程结束变为僵尸进程后,会获取子进程的退出信息,并将它销毁返回。如果我们不需要查看子进程的退出状态,那么参数设置为NULL就可以了,如果需要获取子进程的退出信息就传入一个int引用就好了。如果成功wait函数会返回子进程的pid,如果当前进程没有子进程,就会失败,返回-1并设置errno为ECHILD。

       对于waitpid函数来说,它的作用和wait函数是一样的,只不过多了两个参数,可以让用户更灵活的去操作。首先第一个参数是一个pid(进程号),它有四种形式:

        1. 当pid > 0时,只等待进程ID等于pid的子进程,那么此时的waitpid函数就有了针对性,只等待和pid相同进程号的子进程。

        2. 当pid = -1时,等待任何一个子进程退出,那么此时的waitpid函数和wait函数的作用相同。

        3. 当pid = 0时,等待和父进程相同进程组中的任何子进程。

        4. 当pid < -1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。

       这里再解释一下什么是进程组ID,对于一个父进程,它的进程组ID就是它本身的PID,那么它的所有子进程的组进程ID也都是父进程的PID(可以理解为一个家族),那么假如一个父进程的PID为1000,子进程的PID为1001、1002、1003....,那么这父子进程的组进程ID都是1000,那么我们在使用waitpid函数时,设置pid为-1000的意思就是等待组进程ID为1000的子进程结束。

       第三个参数options,有时候父进程一直阻塞在那里会导致程序的性能降低,那么我们在第三个参数上使用WNOHANG的话,此时如果子进程还正在运行,父进程不会阻塞在这里并返回0,如果子进程已经结束,返回子进程的PID,如果没有子进程返回-1并设置errno。

waitpid有wait没有的三个功能:

1. waitpid能等待一个特定的子进程,而wait只能等待任意的子进程。

2. 系统一旦调用wait函数就会阻塞父进程来等待,直到子进程的退出才停止阻塞,而waitpid提供了非阻塞方式的等待,也就是          WNOHANG参数。

3. waitpid支持作业控制,提供用于检查wait和waitpid返回状态的宏,这两个函数返回的子进程的状态都保存在status指针中。
    WIFEXITED(status): 若为正常终止, 则为真. 此时可执行 WEXITSTATUS(status): 取子进程传送给exit或_exit参数的低8位。
    WIFSIGNALED(status): 若为异常终止, 则为真.此时可执行 WTERMSIG(status): 取使子进程终止的信号编号。
    WIFSTOPPED(status): 若为当前暂停子进程, 则为真. 此时可执行 WSTOPSIG(status): 取使子进程暂停的信号编号。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值