专栏首页Zaqdt_ACMLinux孤儿进程和僵尸进程详解(wait和watipid)

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 条评论
登录 后参与评论

相关文章

  • Codeforces 1038A. Equality(水题)

    题目链接:http://codeforces.com/problemset/problem/1038/A

    Ch_Zaqdt
  • Linux系统编程fork详解

           使用fork函数会创建一个和父进程相同的子进程。在调用了fork函数后,会先为子进程申请一个PID号,然后申请一个PCB结构,然后将父进程的PCB...

    Ch_Zaqdt
  • K.Upside down primes(求大素数--米勒卡宾算法--Miller_Rabin)

           题意是给一串数字,然后对这串数字进行180度翻转,其中1,2,5,8,0翻转完还是它本身,6翻转完是9,9翻转后是6,3,4,7都无法翻转(直接输...

    Ch_Zaqdt
  • 操作系统笔记【进程管理及控制

    顺序执行:单道批处理的执行方式,也用于简单的单片机系统,具有独立功能的程序独占cpu直到得到最终结果的过程

    BWH_Steven
  • 多进程的组织、交替、合作

    上文中(操作系统之进程管理(1):从CPU如何执行进程说起),我们说过操作系统为每个程序提供了一个叫做PCB(Process Control Block进程控制...

    xujjj
  • [每天五分钟,备战架构师-2]操作系统基本原理

    操作系统是管理和控制计算机硬件和软件资源的计算机程序,是直接运行在“裸机”上的最基本的系统软件。注意,这里说的裸机可以是物理机,也可以是虚拟机。随着技术的发展,...

    大江小浪
  • 进程

    以前的计算机一次只能执行一个程序,后来有了多道程序设计的电脑,可以宏观上执行多个程序。由此产生了进程的概念

    用户5426759
  • linux进程管理

    查看进行使用的指令是 ps ,一般来说使用的参数是 ps -aux,ps -ef,正常与grep连用

    小小咸鱼YwY
  • python-multiprocessing-Pool进程池—-多进程

    进程池是用来创建和管理进程的一个池子,池子里面可以有很多的进程,它是进程工作的容器

    kirin
  • Linux进程基础

      计算机实际上可以做的事情实质上非常简单,比如计算两个数的和,再比如在内存中寻找到某个地址等等。这些最基础的计算机动作被称为指令(instruction)。所...

    用户6754675

扫码关注云+社区

领取腾讯云代金券