Linux之回收子进程

回收子进程

孤儿进程

父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为 init 进程,称为 init进程(进程ID为1)领养孤儿进程
【验证孤儿进程】

#include <stdio.h>
#include <unistd.h>

int main()
{
	pid_t pid;
	pid = fork();
	if(pid < 0)
	{
		perror("fork error\n");
		return -1;
	}
	else if(pid > 0)
	{
		sleep(1);
		printf("I am parent, pid = %u, ParentID = %u\n", getpid(), getppid());
	}
	else
	{
		printf("I am child, parentID = %u\n", getppid());
		sleep(5);
		printf("I am child, parentID = %u\n", getppid());
	}
	return 0;
}

【运行结果】
在这里插入图片描述
查看一下1487是什么进程?
在这里插入图片描述

【结果分析】
孤儿进程被 1号进程init 收养,但是这里为什么孤儿进程被1487进程收养了呢?重复执行几次都是这个结果,每次都是2197,并不是书上说的 1 。看来2197也是个特殊的进程。 我们用 ps 查看一下这个进程: 它对应的/sbin/upstart ,这是个什么鬼? 于是求助度娘得到答案:原来 upstart 是init 演进来的,可以说是一种新型的 init系统,传统的 sysvinit 已经淡出历史舞台,新系统 UpStart 和 systemd 各有特点,而越来越多的 Linux 发行版采纳了 systemd。 ubuntu 较新的发行版都用 upstart 代替 init 来收养孤儿进程。

僵尸进程

进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸进程

【代码】
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>

int main()
{
	pid_t pid;
	pid = fork();
	if(pid < 0)
	{
		perror("fork error\n");
		return -1;
	}
	else if(pid == 0)
	{
		printf("---child, parentID = %u, going to sleep 10s\n", getppid());
		sleep(10);
		printf("---child, I am die\n");
	}
	else
	{
		while(1)
		{
			printf("I am parent, ID = %u", getpid());
			sleep(1);
		}
	}
	return 0;
}

【运行结果】
在这里插入图片描述

【注意】僵尸进程是不能用kill命令清除掉的。因为kill命令只是用来终止进程的,而僵尸进程已经终止
【思考】有什么办法可以清除掉僵尸进程呢?
【答案】父进程在子进程结束后将其回收; 或者将其父亲杀死,让其变成孤儿进程,然后由init进程领养,init进程发现它是一个僵尸进程,自然会将它回收

wait函数

  • 一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一写信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变量 $? 查看,因为Shell是它的父进程,当它终止时Shell调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程
  • 父进程调用wait函数可以回收子进程终止信息。该函数有三个功能
  1. 阻塞等待子进程退出
  2. 回收子进程残留资源
  3. 获取子进程结束状态(退出原因)
pid_t wait(int *status);
【返回值】成功:清理掉的子进程ID;失败:-1(没有子进程)
  • 当进程终止时,os的隐式回收机制会:
  1. 关闭所有的文件描述符
  2. 释放用户空间分配的内存。内核的PCB仍存在。其中保存着该进程的退出状态。(正常终止->退出值;异常终止->终止信号)
  • 可使用wait函数传出参数status来保存进程的退出状态。借助宏函数来进一步判断进程终止的具体原因。宏函数可以分为如下三组:
1. WIFEXITED(status) 为非0 -> 进程正常结束
   W IF EXITED
   WEXITSTATUS(status) 如上宏为真,使用此宏 -> 获取进程退出状态(exit参数)
2. WIFSIGNALED(status) 为非0 -> 进程异常终止
   WTERMSIG(status) 如上宏为真,使用此宏 -> 取得使进程终止的那个信号的编号
*3. WIFSTOPPED(status) 为非0 -> 进程处于暂停状态
	WSTOPSIG(status) 如上宏为真,使用此宏 -> 取得使进程暂停的那个信号的编号	
	WIFCONTINUED(status) 为真 -> 进程暂停后已经继续运行

【代码】

//子进程正常终止
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>

int main()
{
	pid_t pid, wpid;
	pid = fork();
	int status;
	if(pid < 0)
	{
		perror("fork error\n");
		return -1;
	}
	else if(pid == 0)
	{
		printf("---child, parentID = %u, going to sleep 1s\n", getppid());
		sleep(1);
		printf("---child, I am die\n");
		exit(2);
	}
	else
	{
		wpid = wait(&status);
		if(wpid == -1) //判断wait返回值是不是子进程ID,是则回收成功
		{
			perror("wait error\n");
			exit(1);
		}
		if(WIFEXITED(status))
		{
			printf("child exit with %d\n", WEXITSTATUS(status));
			
		}
		if(WIFSIGNALED(status))
		{
			printf("child killed by %d\n", WTERMSIG(status));
		}
		
		while(1)
		{
			printf("I am parent, ID = %u\n", getpid());
			sleep(1);
		}
	}
	return 0;
}

【执行结果】没有僵尸进程存在
在这里插入图片描述在这里插入图片描述

【练习】使用kill将子进程杀死,查看status状态

//子进程异常终止
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>

int main()
{
	pid_t pid, wpid;
	pid = fork();
	int status;
	if(pid < 0)
	{
		perror("fork error\n");
		return -1;
	}
	else if(pid == 0)
	{
		printf("---child, parentID = %u, going to sleep 1s\n", getppid());
		sleep(20);
		printf("---child, I am die\n");
	}
	else
	{
		wpid = wait(&status);
		if(wpid == -1) //判断wait返回值是不是子进程ID,是则回收成功
		{
			perror("wait error\n");
			exit(1);
		}
		if(WIFEXITED(status))
		{
			printf("child exit with %d\n", WEXITSTATUS(status));
			
		}
		if(WIFSIGNALED(status))
		{
			printf("child killed by %d\n", WTERMSIG(status));
		}
		
		while(1)
		{
			printf("I am parent, ID = %u\n", getpid());
			sleep(1);
		}
	}
	return 0;
}

【运行结果】
在这里插入图片描述

waitpid 函数

作用同wait,但可指定pid进程清理,可以不阻塞

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

【返回值】成功:返回清理掉的子进程ID;失败:-1(无子进程)

【特殊参数和返回情况】
参数 pid:
	> 0  回收指定ID的子进程
	-1   回收任意子进程(相当于wait)
	0    回收和调用waitpid一个组的所有子进程
	< -1 回收指定进程组内的任意子进程
返回0:参数三为 WNOHANG(非阻塞),且子进程正在运行

【注意】一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环
【练习一】创建3个子进程,调用一次waitpid函数,发现只回收一个子进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>

int main()
{
	pid_t pid;
	int i;
	int status;
	for(i = 0; i < 3; i++)
	{
		pid = fork();
		if(pid < 0)
		{
			perror("fork error\n");
			return -1;	
		}
		else if(pid == 0)
			break;
	}
	if(i == 3)
	{
		sleep(i);
		printf("I am parent\n");
		waitpid(pid, &status, WNOHANG);
		//while();
		if(WIFEXITED(status))
		{
			printf("child exit with %d\n", WEXITSTATUS(status));
		}
		if(WIFSIGNALED(status))
		{
			printf("child exit with %d\n", WTERMSIG(status));
		}
		while(1);
	}
	else
	{
		sleep(i);
		printf("I am %dth child, pid = %d\n", i + 1, getpid());
	}
}

【运行结果】父进程只回收了进程号为29869的子进程,而且父进程回收哪个子进程是不确定的
在这里插入图片描述

【练习2】使用waitpid阻塞回收全部子进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>

int main()
{
	pid_t pid;
	int i;
	int status;
	for(i = 0; i < 3; i++)
	{
		pid = fork();
		if(pid < 0)
		{
			perror("fork error\n");
			return -1;	
		}
		else if(pid == 0)
			break;
	}
	if(i == 3)
	{
		sleep(i);
		printf("I am parent\n");
		while(waitpid(-1, &status, 0)); //== wait(NULL)
		while(1);
	}
	else
	{
		sleep(i);
		printf("I am %dth child, pid = %d\n", i + 1, getpid());
	}
}

【运行结果】没有存在僵尸进程
在这里插入图片描述

【练习3】使用waitpid非阻塞回收全部子进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>

int main()
{
	pid_t pid, wpid;
	int i;
	int n = 3;
	int status;
	for(i = 0; i < 3; i++)
	{
		pid = fork();
		if(pid < 0)
		{
			perror("fork error\n");
			return -1;	
		}
		else if(pid == 0)
			break;
	}
	if(i == 3)
	{
		sleep(i);
		printf("I am parent\n");
		do{
			//非阻塞回收全部子进程
			wpid = waitpid(-1, &status, WNOHANG);	
			if(wpid > 0) //回收一个子进程成功
			{
				n--;
			}
			// if wpid = 0 说明子进程正在运行
			sleep(1);
			
		}while(n > 0);
		printf("wait finish\n");
	}
	else
	{
		sleep(i);
		printf("I am %dth child, pid = %d\n", i + 1, getpid());
	}
}

【运行结果】
在这里插入图片描述

【练习】使用waitpid回收指定子进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>

int main()
{
	pid_t pid, wpid;
	int i;
	int n = 3;
	int status;
	for(i = 0; i < 3; i++)
	{
		pid = fork();
		if(pid < 0)
		{
			perror("fork error\n");
			return -1;	
		}
		else if(pid == 0)
			break;
		else
		{
			if(i == 1)
			{
				wpid = pid;
			}
		}
	}
	if(i == 3)
	{
		sleep(i);
		printf("I am parent\n");
		waitpid(wpid, &status, 0);
		while(1);
	}
	else
	{
		sleep(i);
		printf("I am %dth child, pid = %d\n", i + 1, getpid());
	}
}

【运行结果】
在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在中,如果一个子进程先于进程退出,而进程没有对子进程的退出结果进行读取,那么该子进程会成为孤儿进程。孤儿进程会被1号init进程领养,并且进入僵尸状态,由1号init进程负责处理和回收。 为什么要回收孤儿进程呢?因为对于任何一个进程来说,当它被执行起来时,系统会为其分配一块虚拟地址空间。但是当进程退出时,它只能释放自己用户区的资源。如果不及时回收孤儿进程,就会造成内存泄漏,导致系统的虚拟地址空间资源耗尽。因此,为了保证系统资源的正常使用,孤儿进程回收是必要的。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [【Linux操作系统】孤儿进程](https://blog.csdn.net/weixin_63449996/article/details/130533945)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [Linux:虚拟地址空间,程序和进程,创建并回收子进程,孤儿进程和僵尸进程。为什么要回收子进程?](https://blog.csdn.net/qq_51004011/article/details/126407281)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值