首页 > 系统相关 >17.进程回收

17.进程回收

时间:2023-08-28 16:59:03浏览次数:45  
标签:status 17 pid 回收 进程 include waitpid wait

17.进程回收

1.为什么要进行进程资源的回收

当一个进程退出之后,进程能够回收自己的用户区的资源,但是不能回收内核空间的PCB资源,必须由它的父进程调用wait或者waitpid函数完成对子进程的回收,避免造成系统资源的浪费。

2.孤儿进程

  • 孤儿进程的概念:

若子进程的父进程已经死掉,而子进程还存活着,这个进程就成了孤儿进程。

  • 为了保证每个进程都有一个父进程,孤儿进程会被init进程领养,init进程成为了孤儿进程的养父进程,当孤儿进程退出之后,由init进程完成对孤儿进程的回收。

  • 模拟孤儿进程的案例

编写模拟孤儿进程的代码讲解孤儿进程,验证孤儿进程的父进程是否由原来的父进程变成了init进程。

测试1: 孤儿进程测试

/************************************************************
  >File Name  : orphan.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年05月19日 星期四 20时53分41秒
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
	pid_t pid = fork();
	if(pid == 0)
	{
		while(1)
		{
			printf("child: %d, ppid: %d\n", getpid(), getppid());
			sleep(1);
		}
	}
	if(pid > 0)
	{
		printf("parent: %d\n", getpid());
		sleep(3);	
	}
	return 0;
}

我们看到,子进程的父进程ID在3秒后变成了1,这说明父进程结束后,它变成了孤儿进程,并被init进程收养,使用kill命令基于可以杀死孤儿进程。

3.僵尸进程

  • 僵尸进程的概念:

若子进程死了,父进程还活着,但是父进程没有调用wait或waitpid函数完成对子进程的回收,则该子进程就成了僵尸进程。

  • 如何解决僵尸进程

▶由于僵尸进程是一个已经死亡的进程,所以不能使用kill命令将其杀死

▶通过杀死其父进程的方法可以消除僵尸进程。杀死其父进程后,这个僵尸进程会被init进程领养,由init进程完成对僵尸进程的回收。

  • 模拟僵尸进程的案例

编写模拟僵尸进程的代码讲解僵尸进程,验证若子进程先于父进程退出,而父进程没有调用wait或者waitpid函数进行回收,从而使子进程成为了僵尸进程。

测试2: 僵尸进程测试

/************************************************************
  >File Name  : zombie.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年05月19日 星期四 20时54分20秒
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
	pid_t pid = fork();
	if(pid == 0)
	{
		printf("child: %d, ppid: %d\n", getpid(), getppid());	
		sleep(1);
	}
	if(pid > 0)
	{
		while(1)
		{
			printf("parent: %d\n", getpid());	
			sleep(1);
		}
	}
	return 0;
}

我们可以通过ps命令查看僵尸进程

图中红色标出的三个地方Z+、[]、default都可以表明这是僵尸进程,另外Z+是进程类型的一个表示,可以通过 man ps 查看,我们可以通过 man ps 进入帮助手册,然后在命令行输入 /zombie 来搜索zombie相关的信息。

僵尸进程是不能用kill杀死的,因为kill命令是终止进程,而僵尸进程已经终止了。我们知道僵尸进程的资源需要由父进程去回收,那么我们在这种情况下如何回收僵尸进程的资源呢?方法就是杀死父进程,父进程被杀死后,由init接管子进程并回收子进程资源。

4.进程回收函数

4.1wait()函数

 ▶函数原型:

pid_t wait(int *status);

 ▶函数作用:

  ▷阻塞并等待子进程退出

  ▷回收子进程残留资源

  ▷获取子进程结束状态(退出原因)。

 ▶返回值:

  ▷成功:清理掉的子进程ID;

  ▷失败:-1 (没有子进程)

 ▶status参数:子进程的退出状态 -- 传出参数

  ▷WIFEXITED(status):为非0 → 进程正常结束

  WEXITSTATUS(status):获取进程退出状态

  ▷WIFSIGNALED(status):为非0 → 进程异常终止

  WTERMSIG(status):取得进程终止的信号编号。

一个进程在终止的时候会关闭所有的文件描述符,释放在用户空间分配的内存,但是它的PID还保留着,内核在其中保存了一些信息:如果进程是正常终止则保存进程退出状态;如果进程是异常终止,则保存导致该进程终止的那个信号。这个进程的父进程可以调用wait()或者waitpid()来获取这些信息,然后彻底清除这个进程。我们知道,一个进程的退出状态可以在shell中用特殊变量$?查看,因为shell进程是它的父进程,当它终止的时候shell调用wait()或waitpid()得到它的退出状态,同时彻底清除这个进程。父进程调用wait()函数可以回收子进程终止信息,wait()函数功能主要有三个:阻塞等待子进程退出;回收子进程残留资源;获取子进程退出状态(退出原因)。

4.1.1包含头文件及函数原型

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

pid_t wait(int *status);

/*
pid_t waitpid(pid_t pid, int *status, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
*/

4.1.2函数描述

wait()函数用于回收子进程,获取子进程的终止原因,如果子进程没有终止,那么将会阻塞等待子进程的终止。

4.1.3函数参数

  • status:传出参数(C语言一级指针做输出)
WIFEXITED(status)	/*wait if exited 等待是否退出*/
WEXITSTATUS(status) /*wait exit status 退出原因*/
    
WIFSIGNALED(status) /*wait if signaled 是否被信号杀死*/
WTERMSIG(status) 	/*wait term sugnaled 被几号信号杀死的*/
    
WCOREDUMP(status)    
WIFSTOPPED(status)
    
WSTOPSIG(status)    
WIFCONTINUED(status)     
  • 根据status判断子进程终止原因

WIFEXITED(status)判断子进程是否正常退出;

WIFEXITED(status)为真表示正常退出,使用WEXITSTATUS(status)获取退出状态;

WIFEXITED(status)非真,表示非正常退出,使用WIFSIGNALED(status)判断是否被信号杀死;

WIFSIGNALED(status)为真,表示是被信号杀死,使用WTERMSIG(status) 获取杀死进程的信号;

4.1.4函数返回值

  • on success, returns the process ID of the terminated child; wait()函数成功返回终止的子进程的ID.
  • on error, -1 is returned. 失败返回-1.

案例测试: wait()获取子进程退出原因

/************************************************************
  >File Name  : wait_test.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年05月19日 星期四 22时45分28秒
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char* argv[])
{
    pid_t pid = fork();
    if(pid == 0)
    {
        printf("child: %d, ppid: %d\n", getpid(), getppid());
        sleep(3); /*子进程睡眠3秒,那么父进程中的wait函数会阻塞3秒,一直等到子进程退出*/
        return 66; /*正常退出,这个值可以被WEXITSTATUS获取到,这个值是有范围的*/
        /*exit(66); 也表示正常退出*/
    }
    if(pid > 0)
    {
        int status;
        pid_t wpid = wait(&status);
        printf("wpid: %d, cpid: %d\n", wpid, pid);
        if(WIFEXITED(status)) /*进程正常退出,获取退出原因*/
        {
            printf("child exit because: %d\n", WEXITSTATUS(status));
        }
        else /*非正常退出*/
        {
            if(WIFSIGNALED(status)) /*为真表示被信号杀死*/
            {
                printf("signal is: %d", WTERMSIG(status));
            }
            else
            {
                printf("other...\n");
            }
        }
        while(1)
        {
            sleep(3);
        }
    }
    return 0;
}

我们首先演示一下子进程的正常退出,并获取退出状态,子进程的退出状态可以用return或者exit来传递。

在代码中,当 fork() 被调用时,它创建了一个子进程,这个子进程是父进程的副本。然后,两个进程(父进程和子进程)从 fork() 的返回位置开始并行执行。由于它们是并发执行的,理论上,父进程和子进程的代码都有机会首先运行。

然而,通常情况下,我们会看到子进程的输出先于父进程的输出,有以下几个原因:

  1. 调度器决策:操作系统的调度器可能决定让新创建的子进程先运行,尤其是当系统资源充足,且没有其他高优先级的任务时。

  2. 输出缓冲printf 函数通常使用缓冲输出。这意味着即使父进程或子进程先执行其 printf 语句,输出可能不会立即显示在屏幕上或写入文件中。子进程在 printf 之后进入 sleep 状态,这可能导致其输出被刷新到屏幕上。而父进程在其 printf 之后立即调用了 wait(),可能在子进程结束之前都不会刷新其输出缓冲区。

  3. 运行环境的影响:具体哪个进程先输出还取决于运行环境、系统的当前负载、其他运行中的进程和线程以及其他一些因素。

虽然在许多情况下,子进程的输出可能会先于父进程显示,但这并不是一个固定的规则。如果多次运行同一段代码,可能会看到不同的顺序,尤其是在高负载的系统或具有多个CPU核心的系统上。因此,除非有明确的同步机制,否则不能保证并发进程的执行顺序。

下面我们在子进程中增加一个循环,然后用信号杀死子进程

if (pid == 0)
{
    printf("child: %d, ppid: %d\n", getpid(), getppid());
    sleep(2); /*子进程睡眠3秒,那么父进程中的wait函数会阻塞3秒,一直等到子进程退出*/
    while (1)
    {
        printf("child: %d, ppid: %d\n", getpid(), getppid());
        sleep(1);
    }
}

重新编译运行,并开启另一个shell,使用 kill -9 杀死子进程

获取到杀死进程的信号,正好是9号信号,如果直接使用 kill pid 默认使用的是15号信号。

4.2waitpid()函数

  • waitpid函数

 ▶函数原型:

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

 ▶函数作用

同wait函数

 ▶函数参数

  参数:

  pid:

   pid = -1 等待任一子进程。与wait等效。

   pid > 0 等待其进程ID与pid相等的子进程。

   pid = 0 等待进程组ID与目前进程相同的任何子进程,也就是说任何和调用waitpid()函数的进程在同一个进程组的进程。

   pid < -1 等待其组ID等于pid的绝对值的任一子进程。(适用于子进程在其他组的情况)

  status: 子进程的退出状态,用法同wait函数。

  options:设置为WNOHANG,函数非阻塞,设置为0,函数阻塞。

 ▶函数返回值

  >0:返回回收掉的子进程ID;

  -1:无子进程

  =0:参3为WNOHANG,且子进程正在运行。

  • waitpid函数练习

使用waitpid函数完成对子进程的回收

4.2.1包含头文件及函数原型

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

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

4.2.2函数描述

The waitpid() system call suspends execution of the calling process until a child specified by pid argument has changed state.

waitpid() 系统调用暂停调用进程的执行,直到由 pid 参数指定的子进程改变了状态。

4.2.3函数参数

  • pid:

 ■小于 -1:meaning wait for any child process whose process group ID is equal to the absolute value of pid. 回收一个组的子进程,使用时把组ID(一般是父进程ID)传给pid参数,就可以使用waitpid()回收这个进程组的所有子进程。

 ■-1:meaning wait for any child process. 回收所有,任何子进程,这是最常用的取值,把所有子进程都回收。

 ■0:meaning wait for any child process whose process group ID is equal to that of the calling process. 回收和调用进程组ID相同的组内的子进程。

 ■大于0:meaning wait for the child whose process ID is equal to the value of pid. 回收指定的进程pid。

  • status:传出参数,同wait()函数
  • options:选项

 ■WNOHANG: return immediately if no child has exited. wait no hang,如果子进程没有结束,立即返回,不会挂起等待(wait函数如果子进程没有退出会阻塞等待)。如果options参数填0,那么和wait()函数一样会挂起等待子进程结束。

 ■WUNTRACED: also return if a child has stopped (but not traced via ptrace(2)). Status for traced children which have stopped is provided even if this option is not specified.如果子进程已停止(但未通过 ptrace(2) 进行跟踪)则返回。即使没有指定此选项,仍会提供已停止的被跟踪子进程的状态。

 ■WCONTINUED: also return if a stopped child has been resumed by delivery of SIGCONT.如果一个已停止的子进程因接收到 SIGCONT 信号而恢复,则返回。

  • 函数返回值

 ■on success, returns the process ID of the child whose state has changed; if WNOHANG was specified and one or more child(ren) specified by pid exist, but have not yet changed state, then 0 is returned. 如果设置了WNOHANG选项,并且没有子进程退出则返回0,如果有子进程退出则返回退出子进程的pid。
 ■On error, -1 is returned. 比如说没有子进程或子进程早就全部结束了,可能就会出错返回-1。

下面通过例子演示waitpid()函数的用法。

/************************************************************
  >File Name  : waitpid_test.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年05月20日 星期五 16时31分35秒
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char* argv[])
{
    pid_t pid = fork();
    if(pid == 0)
    {
        printf("child: %d\n", getpid());
        sleep(2);
    }
    if(pid > 0)
    {
        printf("parent: %d\n", getpid());
        int ret = waitpid(-1, NULL, WNOHANG);
        printf("ret: %d\n", ret);
        while(1)
        {
            sleep(1);
        }
    }
    return 0;
}

为什么使用了waitpid()函数还会产生僵尸进程呢,这是因为在waitpid()函数中使用了选项参数WNOHANG,而子进程中有一个睡眠函数,子进程睡眠的时候,父进程中waitpid()语句没有等到子进程结束就执行了,由于WNOHANG选项参数的存在,waitpid不会阻塞等待之进程结束,而是直接返回。当waitpid()返回父进程中后,子进程才结束,但是waitpid()已经执行完了,所以并没有回收子进程,子进程因此变成僵尸进程。

解决方法就是在一个循环中执行waitpid()函数,直到ret不等于0的时候说明子进程退出了,跳出循环。

5.回收多个子进程

上面使用wait()函数和waitpid()函数举的例子都是回收一个子进程,有时候我们可能需要回收多个子进程,下面介绍回收多个子进程的方法。

5.1 使用wait()回收多个子进程

首先使用wait()函数来回收多个子进程,我们可以在一个for循环中等待子进程的结束,创建了几个子进程就for循环等待几次,代码如下。

/************************************************************
  >File Name  : mutipwait.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年05月20日 星期五 17时23分57秒
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char* argv[])
{
    int i = 0;
    pid_t pid;
    for(i = 0; i < 5; i++)
    {
        pid = fork();
        if(pid == 0)
        {
            printf("child: %d\n", getpid());
            break;
        }
    }
    sleep(i);
    if(i == 5) /*只有父进程可以执行到i=5*/
    {
        for(i = 0; i < 5; i++)
        {
            pid_t wpid = wait(NULL);
            printf("wpid: %d\n", wpid);
        }
        while(1)
        {
            sleep(1);
        }
    }
    return 0;
}

编译运行,可以看到所有子进程都被回收。

5.2使用waitpid()回收多个子进程

如果使用waitpid()函数,可以借助函数的参数和返回值去判断每个子进程是否回收成功。

/************************************************************
  >File Name  : mutipwaitpid.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年05月20日 星期五 17时45分39秒
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char* argv[])
{
    int i = 0;
    pid_t pid;
    for(i = 0; i < 5; i++)
    {
        pid = fork();
        if(pid == 0)
        {
            break;
        }
    }
    if(i == 5) /*只有父进程可以执行到i=5*/
    {
        printf("parent: %d\n", getpid());
        while(1) /*无限循环保证所有子进程全部回收*/
        {
            pid_t wpid = waitpid(-1/*回收任何子进程*/, NULL, WNOHANG);
            if(wpid == -1)
            {
                break; /*如果返回-1说明已经没有子进程了,退出循环*/
            }
            if(wpid > 0)
            {
                printf("wpid: %d\n", wpid); /*打印被回收的子进程的ID*/
            }
        }
        while(1)
        {
            sleep(1);
        }
    }
    if(i < 5) /*说明是子进程*/
    {
        printf("no. %d child: %d\n", i, getpid());
    }
    return 0;
}

编译执行,可以看到所有进程都被回收了

参考:

【Linux进程】六、wait()函数——子进程回收

标签:status,17,pid,回收,进程,include,waitpid,wait
From: https://www.cnblogs.com/codemagiciant/p/17662768.html

相关文章

  • 实用指令_实操作_进程服务管理
    服务(service)管理服务本质就是进程,但是是运行在后台的,通常都会监听某个端口,等待其他程序的请求,比如(mysql,sshd防火墙等),因此我们又称为守护进程,是linux中非常重要的知识点service管理指令service服务名[start|stop|restart|reload|status]systemctlCento7以后基础......
  • 实用指令_实操作_进程管理_进程监控网络监控
    动态监控进程top与ps命令很相似,它们都用来显示正在执行的进程。类似于任务管理器。top与ps最大的不同之处,在于top在执行一段时间可以更新正在运行的进程。基本语法top[选项]选项说明选项功能-d秒数指定top命令每隔几秒更新。默认是3秒在top命令的交互......
  • 实用指令_实操作_进程管理
    进程管理基本介绍在linux中,每个执行的程序(代码)都称为一个进程。每一个进程都分配一个id号每一个进程,都会对应一个父进程,而这个父进程可以复制多个子进程,例如www服务器每个进程都可能以两种方式存在的。前台与后台,所谓前台进程就是用户目前的屏幕上可操作的。后台进程则是实......
  • 哈希表基础题217. 存在重复元素、389. 找不同、496. 下一个更大元素 I
    217. 存在重复元素1classSolution:2defcontainsDuplicate(self,nums:List[int])->bool:3#方法1:set去重,直接比较去重之后数组长度4iflen(set(nums))!=len(nums):5returnTrue6else:7return......
  • 在 PHP 中,原生并没有提供内置的定时器机制,定时触发的守护进程,其中一个常见的方式是使
    <?phpclassTimerDaemon{private$logfile;private$fp;private$triggerInterval;//触发间隔,以秒为单位private$lastTriggerTime;publicfunction__construct($logfile,$triggerInterval){$this->logfile=$logfile;......
  • Python爬虫追踪新闻事件发展进程及舆论反映
    大家好!在当今信息爆炸的时代,了解新闻事件的发展进程和舆论反映对于我们保持对时事的敏感度和了解社会动态至关重要。在本文中,我将与你分享使用Python爬虫追踪新闻事件发展进程和舆论反映的方法,帮助你获取及时、全面的新闻信息。1.爬取新闻网站首先,我们需要选择合适的新闻网站作为......
  • 进程基础
    1、进程的概念我们编译的代码可执⾏⽂件只是储存在硬盘的静态⽂件,运⾏时被加载到内存,CPU执⾏内存中指令,这个运⾏的程序被称为进程。进程是对运⾏时程序的封装,操作系统进⾏资源调度和分配的基本单位。2、进程的实现中断发⽣后操作系统底层的⼯作步骤1.硬件压......
  • 深入理解操作系统中进程与线程的区别及切换机制(上)
    进程所谓进程,大家可以理解为我们打开的应用程序,如微信、QQ、游戏等,但也有系统应用是我们看不见的,可以打开任务管理器一探究竟,我们写的代码程序在服务器上在不运行的情况下,它就是一个二进制文件,并不是进程!一个进程可以包含一个或者多个线程,但对于CPU来说他就是一个任务而已;在......
  • 80端口被Pid=4进程占用
    原文地址:https://www.cnblogs.com/liqinglucky/p/pid4.html起因是我想安装github加速的软件steamcommunity_302.exe。但是启动软件后提示我80端口被占用的问题。网上找了解决办法443/80端口被占用的解决方案-3的4次方-博客园(cnblogs.com)发现症状确实一样,80端口被一个p......
  • ABC317F题解
    让人头大的数位DP。建议评蓝。个人认为不适合放ABC的F。将三个数二进制拆分,使三个数异或为0相当于每个二进制位三个数中有0或2个是1。所以考虑数位DP,设\(dp[i][m1][m2][m3][lim1][lim2][lim3]\)为第\(i\)位,三个数模\(a\),\(b\),\(c\)分别是\(m1\),\(m2\),\(m3\)......