进程等待进程等待必要性为什么之前讲过子进程退出父进程如果不管不顾就会造成僵尸进程的问题进而造成内存泄漏。另外进程一旦变成僵尸进程进程停止运行不被CPU调度进程只剩下PID和退出码就难以杀死kill -9都没办法因为谁也没用办法去杀死一个已经死去的进程kill -9是信号但是僵尸进程无法被CPU调用就无法接收信号就难以被清理最后父进程派给子进程的任务完成的如何我们需要知道父进程通过进程等待的方式回收子进程资源子进程进程资源 内核级资源、最重要获取子进程退出消息(可选)。进程死亡 进程终止 进程资源回收子进程终止父进程不处理就是不做资源回收子进程就成为僵尸进程了 这也是为什么要进程等待僵尸进程的危害会但用PID资源导致新进程无法创建进程等待的方法是什么父进程创建出子进程后父进程必须要想办法通过上面的接口来等待子进程所以父进程通过使用wait和waitpid的操作就是进程等待通过代码来验证一个是否会产生僵尸进程确实产生了僵尸进程通过进程等待的方式来回收子进程解决僵尸进程这里通过wait来验证我们先来讲讲waitwait函数参数是输出型参数给wait函数传输一个整型变量地址用来存储子进程退出码函数调用成功后父进程就可以通过这个wstatus指针来获取退出码wait()wait是等待那个进程呢是等待任意一个退出的子进程父进程创建一个子进程就等待这一个子进程若创建10个子进程wait就会阻塞掉直到这10个子进程中任意一个退出了就返回那个退出的子进程解决子进程僵尸问题wait函数返回值是回收成功子进程的PID即目标僵尸进程的PID若失败就返回-1如果等待子进程子进程没有退出父进程会阻塞在wait调用处(与scanf同理)可以看到wait回收僵尸进程成功waitpid()这里wait 是简单版等待如果想要获得更多的子进程信息可以使用waitpidpid_t waitpid(pid_t pid,int *wstatus, int options);等待成功返回值是子进程PIDwstatus为输出型参数父进程可以通过这个指针获得子进程的退出码optione是阻塞控制pid为父进程需要等待的子进程pid大于0表示传进来的PID是谁父进程就等这个子进程等于-1 就等价于wait等待任意一个退出的子进程0和小于-1暂时不了解所以上面的案例使用waitpid也可以并且上图这样写id可以写-1-1就和wait一样了效果一致wait/waitpid失败的原因1、进程本身没有子进程2、waitpid传pid参数传错了通过上图案例来看看等待失败的情况我们手动的将子进程的PID1对rid返回值小于0的情况做判断int *wstatus参数这个参数为输出型参数我们知道函数返回数据有两种方式return返回还有一种就是指针传参返回传入指针在函数内部对指针指向地址存储同样达到了return这样向外输出的操作父进程要通过waitpid来获取子进程的退出信息如何评估这个退出信息呢这就是为什么我们要先讲解进程退出了根据进程退出码即main函数返回值来评估我们来试试我们子进程退出码为1wstatus获取的子进程退出信息为什么退出码不为1呢这就说明我们对wstatus的认知有问题要根据实际情况来考虑所以wstatus不只有退出码这个wstatus为整型但是实际上是被划分成多个区域可以当成一个位图来看整型为32B其中高16x不考虑低16x中的15x~8x表示退出码后8位中其中6x~0x表示退出信号7x为core dump高位的core dump和终止信号我们现在不谈要到信号才理解core dump占一个比特位低7个比特位表示的是进程退出时的退出信号默认情况下退出情况为程序运行完毕时core dump和终止信号这八个比特位都为0我们案例的情况这个整型的二进制数位100000000转化为十进制数为256那我们要如何获取我们子进程的退出码呢将这个整型变量右移8位在提取前8位数据所以我们对wstatus做操作(wstatus8)0xFF(0xFF为常数有4个字节二进制数前8位为1后面24位为0),先右移在按位与从而获得所以我们bash通过echo $?获取退出码也是用这个格式来转换的(wstatus8)0xFF面对异常我们就不能演示了因为还没有讲到信号但是我们用过一个信号那就第九个信号其实这些信号都是宏定义进程异常终止就在比特位低7为保存异常时对应的信号编号如果没有异常低7位就全为0因为我们发现信号没有0信号所以就认为终止时候没有收到信号一旦低7位比特位!0就代表着异常退出退出码无意义因为程序没有运行完我们来试试获取子进程运行的退出信号通过使用0x7F常量与wstatus来按位与0x7F二进制数为0111 1111所以按位与可以获取前7个比特位数据我们来制造异常来看看情况如何通过让子进程死循环父进程在等待子进程子进程死循环那就是父进程死循所以我们在其他bash端向子进程发出信号kill -9 PID来终止进程此时我们可以看到waitpid获得的退出信息退出信号为9而退出码为0无意义或者是直接让子进程对0地址赋值出现异常子进程接受异常退出父进程接收退出信息对wstatus分析可以获取退出信号为11子问题理解wait、wstatus位操作宏、option理解wait接口进程具有独立性父进程不能直接拿到子进程的退出信息退出码、退出信号通过全局变量来获取没意义写时拷贝后内存重新分配所以需要wait系统调用接口来实现那这些系统调用是从子进程什么地方拿到退出信息的呢我们要了解系统调用在哪里拿的退出信息就要知道退出信息存在哪里子进程退出进程需要进入僵尸状态虚地址空间和页表可以释放掉但是PCB还要保留所以子进程僵尸只剩下PCB退出信息只能存储在PCB内在PCB内可以预测到int exit_code;int exit_signal;这样的整型数据当子进程退出时OS就会把子进程main的返回值保存到子进程PCB退出信号默认为0若有信号就存储进去父进程通过调用waitpid系统调用通过调用这个接口找到子进程PCB找到子进程的退出信息并通过位操作我们是按位与这里就逆操作按位或赋值给父进程的wstatus父进程从而获取子进程的退出信息这就是waitpid系统调用的实现父进程向系统申请获取子进程退出信息系统就向子进程查找退出信息并将退出信息交给父进程这也解释了之前的系统调用getpid()的调用原理了所以这就是需要僵尸进程的原因wstatus位操作的宏我们知道wstatus整型真正的使用方式和位图类似分为多个区域操作退出码和退出信号的区域分别在8x~15x和0x~6x,我们要获得对应的退出信息要自己做位操作但是OS不想让你操作OS内部有想要的位操作方法我们来了解了解WIFEXITED(wstatus): 判断子进程退出信号是否异常如果异常需要输出信号可以使用wstatus0x7F来获取WEXITSTATUS(wstatus)WIFEXITED(wstatus)为非零提取子进程退出码所以WIFEXITED通常用于成功等待后判断子进程是否异常就像上图等待成功信号无异常就输出rid和退出码信号异常就输出信号异常其实对于具体某个异常信号的获取没有移动要求只有专门调试的时候在需要知道optionwaitpid()内部还有一个参数我们没有提及过那就是int option!这个参数可以设置一些选项默认为0时代表默认阻塞等待其他的选项很多都不用了但是我们要来谈谈选项WNOHANGreturn immediately if no child has exited如果子进程没有退出我们就立即返回即非阻塞调用非阻塞调用我们应该是第一次认识WwaitNO:不要HANG夯住当计算机卡死了做任何操作没有反应就可以说这个计算机被夯住了可以说子进程阻塞了,则WNOHANG就是不夯住就是不阻塞用例子来理解要期末考试了小张没有上课舍友小李是学霸有完整的笔记小张就打电话给小李想找小李给他补课小李说没问题但是要等他一会小张就说没问题然后挂了电话每个一会小张等不及了又打电话给小李小李还没有好小张又挂了小张就这也打了多次电话直到小李下来其中打电话是函数调用小李是OS小张是用户小张打电话就是一次调用打一次电话挂了知道小李没结束是非阻塞查了一次状态是没结束小李什么时候结束也不清楚只能打多次电话 这就是非阻塞轮询访问子进程发现未结束就立即函数返回多次查询后面小张发现又有一科课程要考试了也没上又打电话找小李这次小李同样要等一会小张说可以等你然后说我不挂电话了等你出门了我再挂电话这里小张不挂电话等待直到小李把操作做完才挂电话即用户waitpid访问子进程一直等待子进程结束才返回这就是阻塞调用scanfcin轮询是计算机中的术语所以的轮询在计算机中都是通过循环实现的在循环调用waitpid时每次循环都判断waitpid的返回pidpid 0等待结束pid 0调用结束但是子进程没有退出pid 0等待失败我们再回到非阻塞轮询的例子挂电话期间小张也可以做一些自己的事前看看C语言书、刷手机、发呆等等反正小张可以做自己的事情过来一会再向小李打电话非阻塞调用可以让等待方做做自己的事情再通过一个例子来展示非阻塞调用父进程可以周期性的做自己的事情上面这个例子就展示了在非阻塞调度期间父进程做自己的任务周期性的做自己的任务提高了父进程的效率解放生产力而我们在bash调用sleepsleep也是进程我们发现bash调用sleep后不能调用其他命令这是因为bash在阻塞调用sleepbash等待sleep子进程调用完才继续运行