前言:本节内容仍然是进程的控制,上一节博主讲解的是进程控制里面的进程创建、进程退出、终止。本节内容将讲到进程的等待——等待是为了能够将子进程的资源回收,是父进程等待子进程。 我们前面的章节也提到过等待, 那里的等待是进程等待硬件设备, 而这里是进程等待软件设备,也就是说——进程等待可以发生在硬件上面, 也可以发生在软件上面。 另外, 通过本节的学习, 友友们可以理解一个完整的进程控制是什么样的, 是怎么样的流程。
ps:本节内容适合学习了进程创建、退出与终止的友友们观看
为什么要有进程等待
进程等待, 对于一个子进程来说, 如果子进程退出, 父进程不管不顾, 就可能造成僵尸进程的问题。 进而造成内存泄漏!!!
进程等待的一个原因就是为了子进程能够在僵尸进程的时候被父进程回收, 防止内存泄漏。 ——僵尸进程无法被杀死,需要父进程等待来杀掉它, 从而解决内存泄漏问题。
第二个原因就是我们要通过进程等待获取子进程的退出情况。——知道我布置给子进程的任务, 它完成的怎么样了? ——要么关心, 要么不关心这个是可以选择的。
进程的等待是什么?
进程等待就是通过系统调用, wait/waitpid, 来进行对子进程进行状态检测与回收的功能!
进程等待是怎么实现的?
进程等待是如何实现的用一句话来说就是——父进程通过调用wait/waitpid进行僵尸进程的回收问题!
但是, 这里就有问题, wait接口和waitpid接口是什么? 这就是我们本篇文章着重讲解的知识点。 现在我们先来看一下man手册:
wait是一个系统调用接口, 包含在sys/types.h头文件里。 定义就可以见上图, 然后里面的status参数暂时可以不考虑。
wait和waitpid的作用就是等待一个进程, 当这个进程退出后, 那么父进程就可以等待子进程
, 回收子进程的资源。
wait和waitpid的返回值
返回值如果是大于零的, 那么就是等待成功, 并且是被等待的子进程的pid。
单进程等待
下面是实验(使用wait, waitpid和wait一样, 不用着急, waitpid后续会讲解)
上面的代码就是先创建一个子进程, 然后子进程5s后变成僵尸进程被等待, 父进程10后运行等待, 然后关闭子进程。 但是这个程序很难观察到wait的作用,我们修改一下:
将上图板块中的代码改成图中所示的样子。 然后运行程序, 我们就可以观察到:
多进程等待
上面是单进程的情况, 那么多个子进程的时候, wait等待的是哪一个进程呢? ——wait一次是等待一个进程, 是随机的。 而且只等待一个进程, 那么现在我们来试验一下,下面是代码:
我们运行后, 可以观察到, 5s后, 最后一个创建的子进程都已经是僵尸进程了。
然后随即等待, 全部等待成功后我们就会发现, 进程被回收了。
阻塞等待
那么我们现在思考一下, 当子进程没有变成僵尸, 一直在运行的时候, 父进程等待还有用吗?
现在看下面一个实验, 这里面创建了一个子进程, 这个子进程会无限次循环下去, 但是父进程会在10s后进入等待, 观察现象:
运行结果如下,可以发现, 其实子进程没有进入僵尸, 那么父进程的等待对不它不起效果。——这个其实就是父进程在wait这里发生了阻塞等待!!也就是说, 父进程不会退出, 一直等待子进程的返回。
那么, 什么是阻塞等待——阻塞等待就是子进程不退出, 父进程默认在wait的时候, 也就是调用这个系统调用的时候, 不发生返回, 这个就叫做阻塞等待。
知道了阻塞等待之后, 我们也知道, 任何进程都会变成僵尸进程, 然后被进程等待回收资源, 等待是必须的。 僵尸进程是必须的。——知道了这些, 我们就可以想到一个完整的进程控制代码就是:有创建(fork), 有终止(exit), 有等待(wait)。
waitpid
现在我们来说一下waitpid。 waitpid的功能多于wait, wait是waitpid的子集。
waitpid的第一个参数是等待的进程的pid(也就是说可以指定等待对象), 也可以传送-1, 表示随即等待和wait一样。 并且waitpid的返回值是等待的进程的pid。
退出信息:
退出信息是通过第二个参数, status获取到。 ——wait里面是第一个参数。
对于status, 这个动能可以使用可以不使用——也就是说可以传参, 也可以不传参。 status是一个指针, 意思是说将函数内部的数据通过status带出来, 也就是输出型参数。
并且这个int是被当做及部分使用的。 ——因为int有32给比特位, 其中八个或者十六个比特位表示一个东西, 另外又表示一个东西, 这里我们进行试验一下:
首先我们要写或者退出结果必须顶一个整数变量, 然后取地址变量传给waitpid, 调用系统嗲用后获取相应的结果, 再带出这个结果, 然后status就会发生变化。
运行结果如下:
这里之所以退出2816, 就要考虑几个问题:
我们知道, 进程推出的时候, 主要有三种场景——退出结果正确, 推出结果不正确, 异常。 那么, 子进程退出, 父进程希望获取什么信息呢?
首先:1、子进程代码是否异常?——不同信号表示不同异常
2、没有异常, 结果对吗? exitcode, 不对是因为什么呢? ——不同退出码表示不同原因
那么, status最少能够获取这个进程是否发生了异常, 异常原因, 结果是否正确? 为什么?——这些信息都保存在了status的低16比特位, 并且这里我们只谈status的低16位。
这里的低7位就是代表进程的终止信号, 如果进程异常退出, 这里会保存下来什么异常——比如除零错误, kill -9等等。
这里的第八位是core dam标志——这个暂时不谈。
前八位就是代表正常退出的状态。
而我们的子进程上面是可以看到是正常退出——信号位0; 并且退出码为11, 二进制就是1011, 然后合起来就是0000 1011 0000 0000——这个二进制转化为十进制就是2816.
现在我们来谈一下信号:
我们就会发现, 这些信号里面没有0号信号——没有零号信号, 那么这个时候我们是不是就可以通过这个进程是否是零号信号, 来判断是否发生了异常?
一旦进程发生异常, 那么不同信号就会代表不同的异常, status就会待会不同的结果。
现在, 我们来考虑——父进程要拿到子进程状态数据, 为什么要调用wait等待系统调用呢? 直接用全局变量不行码?——答案是不可以, 我们知道, 父子进程是独立的, 如果在子进程里修改了这个全局变量, 本质上就是发生了写时拷贝, 父进程里面的全局变量并不会被修改。 所以不可以。
现在我们想要将子进程的状态分开打印——打印信号和退出码, 怎么打印呢?
下面是一个代码:
程序和上面一样, 但是打印的内容:
那么我们给一个除零错误, 就会看到:
我们使用kill -9就可以看到:
非阻塞轮询
以上就是本节的全部内容, 下面是本节笔记: