前言
在前面的一篇关于进程状态的文章中谈到Linux中有一种进程是僵尸进程,处于僵尸进程的进程会一直维护着自己的PCB对象以及退出的相关信息,等着父进程来获取,如果父进程一直不来就会导致进程一直处于僵尸进程,占据着内存资源造成内存泄漏 ,而获得子进程的退出信息就要通过进程等待。本文将详细介绍进程等待的各种方式以及底层原理。
为什么要进程等待
在前言部分已经谈到了部分进程等待的必要性,以下对其必要性做个总结:
- 让父进程来获取处于僵尸进程的子进程,对子进程进行回收,防止内存泄漏;
- 进程一旦进入僵尸状态,不能被杀死(kill -9),所以必须通过进程等待的方式来对子进程进行回收;
- 父进程要知道分配给子进程的任务完成的如何 ,必须通过进程等待来获取子进程的退出情况;
- 通过进程等待能够保证子进程在没有完成任务时父进程不会死亡(在后面解释原因),保证父进程是最后一个释放的进程能够获取所有进程的退出信息。
什么是进程等待
进程等待的概念很简单,就是通过系统调用接口wait()和waitpid()让父进程获取子进程的相关信息,让子进程进行释放。关于是什么和为什么都很容易理解,我们将重点放在这些系统调用接口如何使用,以及是如何工作的。
系统调用接口
关于进程等待的系统调用接口有三个:
- pid_t wait(int* status);
- pid_t waitpid(pid_t id,int* status,int options);
- int waitid(idtype_t idtype, id_t id, siginfo_t * infop, int options);
其中使用最多的就是前两种,本文也会重点介绍着两种。
pid_t wait(int* status)
- 返回值类型是pid_t相当于整形,可以存储一个整形。waitpid的返回值就是父进程等待到的子进程的PID;
- 参数有一个:
- status输出型参数 ,通过传址的方式获取子进程的退出数据;
父进程可以选择是否获取子进程的返回数据,如果不进行获取仅仅是为了让子进程释放资源,在传参的时候传NULL即可。
以下是不获取子进程的返回数据的代码:
- status输出型参数 ,通过传址的方式获取子进程的退出数据;
以下是程序运行情况,左边是代码运行的具体情况,右边是一个简单的监视窗口。
现象:fork()创建子进程确实出现了两个进程在同时运行,在子进程运行完毕之后子进程进入了Z状态,即僵尸状态等待父进程来获取子进程的结果,当父进程完成进程等待后子进程确实被销毁了。
通过wat确实可以对子进程进行回收,并且因为wait()参数只有一个,导致wait会等待任意一个子进程不能指定等待哪一个进程;
status
父进程是通过形参来获取子进程的返回状态的,那他是如何通过一个status来获取的呢???
status是一个int* 类型的指针,传值调用和传址调用的主要区别就在于,传址调用是形参的改变会影响实参,所以通过status就可以获取子进程中的数据。
关于进程的终止,在之前的博客中就谈到过:进程终止一共分为3中:
- 进程执行完毕,返回值正确;
- 进程执行完毕,返回值错误;
- 进程异常终止。
因为进程结束的情况有多种可能,这也就意味着status中要能够包含这些情况。
在32位机器下,一个整形有32个比特位,可以通过不同的比特位来代表不同的信息从而将子进程的退出信息存储起来,具体来说就是:
通过将一个整形的不同不为进行分割就可以实现存储不同数据。
通过按位与的操作就可以获取到不同不为的信息:
- (* status)&(0x7F):获取前7位得到异常信息,其中无异常信息用0表示;
- ==(* status>>8)&(0xFF):获取第子进程的退出码。 ==
系统中也有一些宏能帮助我们将status转换为进程退出信息:WIFEXITED(status)检测进程是否正常退出;WEXITSTATUS(status)获取进程的退出码…
status在底层是如何实现的呢???
在子进程中起始已经存储了处于僵尸状态的子进程异常信息已经退出码。通过将这些信息以^按位或的方式拷贝到status上即可。
***能够通过一个全局变量来获取子进程的退出信息???
这是一个典型的错误,因为进程之间具有独立性,即使你使用了一个全局遍历让自己子进程和父进程同时访问,但是当子进程要对全局变量进行修改的时候会进行写实拷贝,无法将信息传给父进程。
pid_t wait()会等待任意一个进程,并且如果子进程还在运行就会让父进程一直等待。
而另一个接口waitpid可以实现指定等待的子进程以及如何等待。
pid_t waitpid(pid_t id,int* status,int options)
waitpid与wait相比,其参数增加了;
- 首先就是id,可以指定父进程等待哪一个子进程了;
- 其次就是options,可以对父进程等待方式进行调整。
关于id,只需要将fork返回给父进程的PID传给waitpid即可实现等待具体的子进程;
关于options,指定等待方式。 wait的等待方式是阻塞等待,如果子进程还在运行,父进程就会一直等;这样势必会浪费时间和资源。
waitpid中可以通过参数来设定是阻塞式等待还是非阻塞式等待:0表示阻塞时等待,WNOHANG表示非阻塞式等待。
如果使用非阻塞式等待,父进程不需要一直等待子进程,父进程也可以先干一些自己的事过一会再来获取释放子进程的资源;这也就使得父进程需要循环式的问子进程时候运行完毕,这种方式被称为非阻塞轮询。
虽然可以以这种方式让父进程边做自己的,边等待;但是我们还是以等待子进程为主,所以不要给父进程分配太多的任务。