进程控制之进程等待

目录

一 为什么要进行进程等待

1 防止内存泄漏

2 得知子进程的状态

二 如何进程等待

1 关于wait

2 waitpid


一 为什么要进行进程等待

1 防止内存泄漏

进程退出,如果父进程不管子进程,子进程就会处于僵尸状态,长时间的僵尸状态会导致内存泄漏。因为没人去回收这个子进程,但是子进程却需要占用资源进行维护。(虽然说如果最后该子进程的父进程也挂掉了之后,就会被操作系统回收。但是大多数情况下这个进程一旦被运行起来,一般都会成为常驻进程,一直在后台运行的。)

2 得知子进程的状态

父进程创建了子进程,是要让子进程办事的,那么子进程把任务完成的怎么样,父进程需要关心的

。并且子进程把任务完成的怎么样,是用之前进程终止的三种情况来标定的。那么子进程如果可以返回上述相关结果的状态,并且让父进程获取到,那么父进程就可以得知子进程把事情办得怎么样了。

因此,父进程了解子进程的状态,回收僵尸状态,都需要通过进程等待来完成。怎么完成呢?就是通过进程等待。进程等待通过让父进程回收子进程,释放子进程相关资源(数据代码和相关的数据结构),并且获得子进程的退出结果,进而指导接下来的工作。

由此看来,进程等待是很有必要的。

需要注意的是:

思考以下问题:如果一个程序在写代码的时候存在内存泄漏的问题,那么如果该程序被运行起来成为了一个进程,该进程退出后,还存在内存泄漏吗?

malloc或者new出来的一块空间,最终进程退出由操作系统自动回收了。所以这是不存在内存泄漏的。

但是如果子进程退出,已经处于僵尸状态,这样的话就并没有随着系统退出而回收资源。

差别:new和malloc在堆上申请空间,僵尸进程本质上是属于操作系统层面的内存泄漏。

所以进程等待是有必要的。进程等待专门用来处理这种僵尸进程引起的内存泄漏。

但是如果父进程不需要获取子进程的状态,并且父进程运行时间很短,也可以不处理僵尸进程。

二 如何进程等待

既然进程等待是很有必要的,那么如何进行对应的操作呢?这里有两种方法:wait和waitpid。这两个函数都可以用来进行进程等待,获取子进程退出时候的一些信息。

进程等待是如何进行的?

我们思考一个问题:能不能直接用一个全局变量标识出子进程的对应退出信息,之后让父进程获取到这个退出信息。为什么非得一定要调用这个函数呢?

本质上还是因为进程之间具有独立性,当标识子进程的状态的时候,由于会修改值,因此会发生写时拷贝,那么父进程就无法读取到了。所以必须要调用这两个函数。

僵尸进程的代码和数据可以被释放了,但是至少会保留进程的PCB信息,这其中保存了进程退出时的退出结果信息。一个进程退出时,退出码和退出信号,会写入PCB结构里面,以便让其他进程读取。

因此,wait读取本质是读取子进程的task_struct结构。并且由于wait和waitpid是内核数据结构对象,是系统调用,所以可以从操作系统中拿到task_struct的退出结果。

1 关于wait

wait主要介绍如何进行进程等待的,以及wait如何使用

wait是一个系统调用接口,它主要是进行阻塞式的等待。(父进程被挂起,一直等待回收子进程的状态,当子进程被回收之后,父进程才会退出)需要包含

#include<sys/types.h>

 函数原型:pid_t wait(int*status);

返回值是pid_t 也就是说当等待成功的时候就会返回子进程的pid,但是如果等待失败的话,就会返回-1.

status之后在介绍waitpid的时候会着重介绍,先把他设置成NULL,表示对这个进程是如何死掉的不太介意。当我们需要了解这个进程死亡的原因的时候就会关注了。

 上述无法观察到从僵尸状态被回收的具体的过程,因为一旦子进程变成了僵尸进程,就被父进程回收了,但是实际上确实是这样子的。

2 waitpid

waitpid和wait的作用是差不多的,他也是用来进行进程等待的。但是相比于wait,他可以等待指定的一个进程,并且等待的方式也不是必须阻塞等待。

waitpid的函数原型:

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

关于返回值:和之前的wait相同,返回特定进程的pid,如果失败就返回-1

关于第一个参数pid_t pid 如果你想等待任意一个进程,那么这个值就可以给-1;但是如果你想要等待一个特定的进程,你就需要传入该进程的pid了。

关于第二个参数:status该参数是一个输出型参数,由操作系统填充。
如果传递NULL,表示不关心子进程的退出状态信息。
否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。

关于进程状态,之前提到过有三种;

代码正常跑完,并且结果正确

代码正常跑完,但是结果不正确

代码还没跑完,程序就崩溃了(有可能是我们发送信号强制终止,也有可能是自己运行崩溃了,即使是这种情况也是被信号终止的)

对于前两种,一般是用退出码来标识的。只有次低八位才表示退出的一个状态。

对于第三种程序崩溃,一般是被信号所杀,用最低七个比特位来标识进程收到的信号。 这种情况下不光光是内部的代码有问题,也有可能是外部直接杀死了进程。所以代码有没有跑完完全是个未知数,她的退出码也就毫无意义了。

 

这些就是基本的一些信号,之后会做介绍,但是我们最关注的是前32个。

演示一下如何使用这个参数 

 

对于这种没有收到退出信号,正常跑完的程序,退出码才有意义。10号退出码表示了他错误的原因,通过查询是因为没有子进程。 

这里测试了一个野指针。当收到了终止信号之后,说明程序不是正常跑完的,那么关注退出码就没意义我们就不去关注她的退出码了

 

由于她的返回机制是这样子设计的,因此我们专门定义了一个宏来实现上述的位移操作,使用这个宏就可以获取到对应的状态了。

①WIFEXITED(status)这个宏主要是为了指出子进程是否是正常退出的,如果是就返回一个非零值。

虽然这个用数字也可以标识,但是如果用数字的话,我们可能会忘记对应的含义,但是用宏的话,就一目了然了。

②WEXITSTATUS(status)当进程异常退出(没跑完就崩了),也就是WIFEXITED为0i,那我们就可以用这个宏来提取子进程的返回值。她的底层实现就是刚才那份代码里的位移操作。

 

关于第三个参数options,他默认是0,标识阻塞等待,他一般是在内核中阻塞,等待被唤醒;如果传入WNOHANG的话就是非阻塞等待。

关于HANG(夯住)这个的理解:夯住就是这个进程没有被CPU调度。要么是在阻塞队列中,要么是正在等待被调度。

WNOHANG其实是一个宏定义 #define WNOHANG 1;

他用来标识非阻塞等待的这样一个状态,被作为参数传递给waitpid接收。waitpid是用C语言写的一个OS自己提供的系统调用接口。非阻塞等待就是父进程不必一直等待子进程返回状态了,在这个期间父进程可以去执行一些其他的任务,采用一种轮巡检测的方案。如果检测到子进程还未退出,父进程就去干自己的事情;如果检测到已退出,就去把这个子进程回收了。

wait和waitpid可以让进程的退出具有一定的顺序性。一定是子进程先退出,父进程才能去回收他。

关于内核代码的实现

waitpid的执行逻辑应该是主要通过检测子进程的退出状态来查看task_struct中的子进程运行信息。

如果子进程的的状态是退出的,那么就会返回子进程的pid;

如果检测到还没退出,就会有相应的代码来看是阻塞等待还是非阻塞等待。如果是阻塞等待,就会挂起父进程。阻塞等待的本质就是进程阻塞在系统函数的内部。如果是非阻塞等待的话,就又有一个EPI寄存器来保存当前的位置,当满足对应的条件的时候,父进程就从寄存器中读取这个位置的数据,方便waitpid重新调用。

如果检测到了其他出错的原因的话,就直接返回-1。

waitpid(child_id, status, flag)
{
	if (status == 退出)
	{
		return child_pid;
	}
	else if (status == 没退出)
	{
		if (flag == 0)
		{
			挂起父进程
		}
		else if (flag == WNOHANG)
		{
			return 0;
		}
	}
	else
	{
		return -1;
	}
}

大概是这样的一个样子 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值