进程控制2——进程等待

在上一小节中我们介绍了进程的创建(fork)与退出(main函数的return与exit函数)
并且要有一个意识,进程退出的时候只有三种情况:
1.进程退出,结果正确
2.进程退出,结果不正确
3.运行异常,收到信号退出

文章目录

  • 1.进程等待
  • 2.wait/waitpid
    • 1). wait
    • 2). waitpid
  • 2.进程等待如何完成它的任务
    • 1). 进程等待解决僵尸进程内存泄漏
    • 2). 进程等待如何收到子进程退出信息?
    • 3). 从系统层面大致了解父进程获取子进程退出信息信息
  • 3.阻塞等待

现在我们还需要认识一个函数_exit函数
进程控制2——进程等待_第1张图片
可以看到它在二号手册里,所以它是系统调用函数。它也能够终止进程。用法跟exit函数一样,那它与exit函数有什么区别呢?代码演示一下:
exit函数
进程控制2——进程等待_第2张图片

在这里插入图片描述
_exit函数
进程控制2——进程等待_第3张图片

在这里插入图片描述
我们发现exit函数会先刷新缓冲区的内容到屏幕上,然后再退出进程,但是
_exit函数是直接退出,没有刷新缓冲区内容,并且exit函数是标准库函数,_exit函数是系统调用函数,由此我们可以得到,exit函数是封装了_exit函数,内部还有诸如刷新缓冲区这样的其他的工作。
进程控制2——进程等待_第4张图片
我们知道库函数,系统调用操作系统内核是这样的关系,exit函数刷新缓存区,_exit函数没有刷新缓存区,那么我们也可以得到一个结论,我们所认知的缓存区不在操作系统内部。这就是进程退出所用到的函数。

说明:虽然_exit的参数是int,但是仅有低8位可以被父进程所用。
所以_exit(-1)时,在终端执行$?发现返回值是255。

1.进程等待

当一个进程退出后没有被回收,那么它就会进入僵尸状态,并且它的父进程不回收它的话,这个进程会一直存在,会发生内存泄漏。那么进程等待的一个任务就是为了解决僵尸状态伴随的内存泄漏。
那么什么是进程等待呢?

它其实是使用wait/waitpid的方式来实现父进程对子进程资源回收的等待过程。

为什么要进程等待?上面已经说过

1.为了解决僵尸状态不回收造成的内存泄漏问题。
2.要知道子进程把任务完成的怎么样,因为什么终止,退出(退出码和接受信号),这一点不是必要的。

比如一些场景下父进程是不需要子进程把任务完成与否,子进程因为什么而退出、终止的。

2.wait/waitpid

1). wait

进程控制2——进程等待_第5张图片
wait是一个系统调用函数,它有一个参数,我们稍后再说。它会回收任意一个子进程并会返回回收进程的id。
我们现在来展示一下任意这两个字的含义:
进程控制2——进程等待_第6张图片
我们这里创建了五个子进程,所以掉用五次wait。
进程控制2——进程等待_第7张图片
可以看到操作系统调度进程是不确定的,wait回收进程也是不确定的。

2). waitpid

进程控制2——进程等待_第8张图片
waitpid可以看到有三个参数,第二个参数和第三个参数也先不说,那么显然它是专门回收特定进程的,当它的参数是下面红框中的那样的时候,它和wait没有区别,当waitpid第一个参数小于零的时候,它也是回收任意一个子进程。
进程控制2——进程等待_第9张图片

进程控制2——进程等待_第10张图片

2.进程等待如何完成它的任务

1). 进程等待解决僵尸进程内存泄漏

我们先搞出一个僵尸进程:
进程控制2——进程等待_第11张图片

然后使用wait回收这个进程:
进程控制2——进程等待_第12张图片

进程控制2——进程等待_第13张图片

2). 进程等待如何收到子进程退出信息?

这就谈论到wait和waitpid中的int* status这个参数了。我们用C语言做题的时候,也会碰到这样的接口:
在这里插入图片描述
这种叫做输出型参数,目的是调用这个接口带回来某些东西,而这里的status带回来的就是退出码和子进程异常终止收到的信号。
话不多说代码展示:
这里退出码是0:
进程控制2——进程等待_第14张图片

进程控制2——进程等待_第15张图片
退出码为10:
进程控制2——进程等待_第16张图片

进程控制2——进程等待_第17张图片
我们看到我们的退出码是10,但是打印出来的是2560。
我们再用一个11号信号来终止子进程:
进程控制2——进程等待_第18张图片

进程控制2——进程等待_第19张图片
发现status的值又是10。
我们说了,这个变量可以接收到退出码和异常终止的信号,而这两个是两个东西,所以我们的status不是一个单纯的int变量,而是一个32个比特位的二进制数字,而我们也只用它的低十六位:
进程控制2——进程等待_第20张图片
那我们现在,来验证一下是否真的是我说的这样呢,我们只需要对status进行位运算就可以:
进程控制2——进程等待_第21张图片

进程控制2——进程等待_第22张图片
确实是我们说的那样。那么说明wait/waitpid确实可以接收退出码和终止信号。
那我们可以这样写:
在这里插入图片描述
进程控制2——进程等待_第23张图片
但是这里介绍两个宏:
在这里插入图片描述
WIFEXITED(status) 若此值为非0 表明进程正常结束。 若上宏为真,此时可WEXITSTATUS(status)获取进程退出状态。

3). 从系统层面大致了解父进程获取子进程退出信息信息

我们知道进程间具有独立性,所以我们无法通过一个简单的变量来获取子进程的退出信息(因为同一个变量,但凡有一个进程改变它的值,都会发生写时拷贝),所以需要系统调用来实现。
子进程在退出的时候会把代码数据销毁之后,将退出信息写到自己的pcb结构体中并将状态改为Z,然后等待父进程回收,而父进程使用wait/waitpid回收后,才可以释放子进程pcb。
进程控制2——进程等待_第24张图片

3.阻塞等待

我们前面的的代码都是父进程等到子进程结束后,再开始回收子进程,那直接回收呢?
进程控制2——进程等待_第25张图片
进程控制2——进程等待_第26张图片
我们看到,父进程回收的时候是一直在等待子进程结束之后才进行的回收,才会执行后续的代码,这就是阻塞等待。这就涉及到waitpid的第三个参数:
当这个参数值为0时,就是阻塞等待。
当这个参数为WNOHANG(一个#define的常量)时,是非阻塞等待:
进程控制2——进程等待_第27张图片
进程控制2——进程等待_第28张图片

在这里我们发现它返回了一个值是0,但是应该是子进程的id才对,这里返回值有了不一样的解释:

返回值大于0:等待成功
返回值等于0:等待成功,但是子进程还没有结束
返回值小于0:等待失败(例如等待一个不存在的进程)

所以当我们使用非阻塞等待时需要以一个循环的方式多次等待,而我们也可以再这个循环里做一些父进程比较简单的任务。

你可能感兴趣的:(服务器,linux,c语言)