目录
进程等待
谁要等待? 等待什么? 为什么要等待 ?
首先要知道进程终止或退出的时候会发生什么, 进程退出时会关闭所有文件描述符, 释放在用户空间分配的内存, 但是PCB却会暂时保留, 里面存着退出状态, 比如一个进程正常退出, PCB里面就放着进程的退出状态(也就是退出码). 如果是异常退出( 前面说到, 肯定是收到信号了), 那么PCB里面存放着导致该进程终止的信号.
子进程的退出状态讲道理是该由父进程回收的( 自己生的孩子, 自己就得负责 ), 也就是说, 父进程必须得等待子进程退出, 接收子进程的退出状态. 如果子进程的退出状态父进程一直没有获取, 那么此时子进程就处于僵死状态, 成为僵尸进程 .
回答开头的问题: 父进程要等待; 等待子进程退出; 为了拿到子进程的退出状态, 防止子进程一直处于僵尸状态
进程等待的必要性
- 要是父进程先于子进程退出, 那么子进程就成了孤儿进程, 1号进程(福利院院长)会立即领养, 但1号进程要想回收, 还是得等子进程退出后再回收.
- 要是子进程先于父进程退出, 退出时为了保存退出状态 , 子进程一些资源并没有释放, 如果此时父进程没有关注到子进程的退出(也就父进程是没有拿到子进程的退出状态), 子进程伤心欲绝, 就会变成 "僵尸进程",变成一个连kill -9 都杀不死的进程, 因为谁也杀不死一个已经死去的进程 (只有重新来过, 也就是重启机器才能解诀僵尸进程), "僵尸进程" 会造成资源泄露, 占用进程数的问题 .
- 僵尸进程和孤儿进程详细在另一篇博客
戳链接( ̄︶ ̄)↗https://blog.csdn.net/qq_41071068/article/details/103231310
简单来说, 就是父进程需要知道, 派给子进程的任务完成的如何, 结果对还是不对,或者是否正常退出. 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息.
说了一大堆, 如何等待 ?
进程等待的方法
-
wait()
头文件 : sys/wait.h
pid_t wait (int* status)
返回值: 成功返回被等待进程PID, 失败则返回 -1. (成功指的是被子进程正常退出, 失败指的是被子程序异常终止)
参数: int* status, 是输出型参数,获取子进程退出状态, 将其退出状态存到status所指向的内存空间, 不关心被子进程退出状态则可以设置status为NULLwait() 是阻塞的, 在wait 返回之前, 阻塞等待子进程退出, 再返回 .
阻塞与非阻塞
- 阻塞: 为了完成一个功能, 若发起调用, 当前不具备完成条件, 则一直等待达到完成条件, 调用才会结束
- 非阻塞: 与阻塞相反, 若发起调用, 当前不具备完成条件, 立即返回
- 最大区别是, 调用是否立即返回
-
waitpid()
pid_ t waitpid (pid_t pid, int *status, int options
参数:
pid :
- pid = -1 :等待任一个子进程
- pid > 0 : 等待其进程ID与pid相等的子进程。
int* status:
- WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
- WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
后面详细看status和WIFEXITED, WEXITSTATUS这两个宏
options: options = 1时, waitpid是非阻塞的, options = 其他值时 waitpid() 是阻塞的
- WNOHANG: 值为1 的宏
返回值
有三种返回值
=-1 : waitpid()出错
= 0 : 没有等到子进程退出
>0 : 等到子进程退出, 返回子进程PID详细来看:
- 给 options 传WNOHANG 或 1, 此时waitpid()是非阻塞的,
1. 如pid此时指定,即pid>0, 如指定pid的进程在调用waitpid()时退出, waitpid()返回子进程PID, 如发现指定pid的进程
还没有退出, 不予等待, 直接返回 0.
2. 若pid不指定,即pid = -1,若在waitpid()调用之前已有子进程退出(任意子进程都可以), 则返回该子进程的PID,
若在调用waitpid()之前没有任何子进程退出, 则返回 0- 若给options传入非1的值, 如等不到子进程退出, 会一直阻塞
- 如果调用waitpid()出错, 则返回-1, 这时errno会被设置成相应的值以指示错误所在 .
PS: 当waitpid是waitpid(-1, &status, 非1) 等效 wait (&status)
不关心子进程退出码时 waitpid(-1, NULL, 非1) 等效 wait (NULL)
参数int* status
前面说到. wait/waitpid都可以获取到退出的子进程的退出状态, 那么获取到的状态在哪儿保存呢 ?
这就需要我们事先申请一块内存空间来保存, 即 int status; 用这个int型变量来保存退出状态, 那么wait/waitpid又如何知道, 要将退
出状态给我们事先申请好的这个变量呢? 传入这个变量的指针, 由操作系统填充.
前面说到, 进程在退出时, 会有正常退出和异常退出(终止), 那么一个int 型变量如何保存这两种不同些情况的的退出状态呢 ?
如下图:
可以看到, 在正常退出时, status这个int型变量, 只有低16位用到了
即, 在正常退出时, 低16位中的高8位存的是子进程得退出码, 后8位全为0,
在异常退出时, 底7位存的是进程的异常退出信号值, 第8位是core dump 标志, 即核心转储文件标志, 为了用户方便查看异常终止
的信息, 此时低16位中的高8位未用
所以, 在wait/waitpid结束后, 要先取出dtatus的低7位看子进程是不是异常终止, 如果不是, 再来看退出码, 如下:
if (!(status & 0x7f)) {
printf("子进程退出码为\n", (status >> 8) & 0xff);
}
else {
printf("子进程异常终止\n");
}
这样写很麻烦, 而且还容易出错, 所以系统就将 !(status & 0x7f) 和 (status >> 8) & 0xff 封装成了两个宏
即WIFEXITED 和 WEXITSTATUS , 既上面的代码可以写成如下:
if (WIFEXITED(status)) {
printf("子进程退出码为\n", WEXITSTATUS(status));
}
else {
printf("子进程异常终止\n");
}
举栗子
wait.c
用wait() 模拟父母接孩子的场景, 父母就像wait() 阻塞一样, 不管多久, 都会等着我们, 直到接到我们.
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main(){
printf("一位爸爸来学校接孩子放学了~\n");
pid_t pid = fork();
if(pid < 0){
perror("fork erro");
exit(-1);
}
else if(pid == 0){
sleep(30);
exit(30);
}
int status = -1;
wait(&status);
if (!(status & 0x7f)) {
printf("%d分钟后接上了孩子\n", (status >> 8) & 0xff);
}
else {
printf("孩子在幼儿园发烧送医院了!\n");
}
return 0;
}
在30秒后, 打印出了30分钟后接上了孩子
如果我们在另一个窗口下用kill -9 杀死子进程, 此时子进程就是异常终止了, 运行如下:
waitpid.c
用waitpid模拟出租车司机在车站等待拉乘客的场景, 出租车司机会一直去询问旅客乘不乘车, 当长的时间拉不到人, 就离开去别的地方拉乘客了
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main(){
printf("一位出租车司机在车站拉乘客~\n");
pid_t pid = fork();
if(pid < 0){
perror("fork erro");
exit(-1);
}
else if(pid == 0){
sleep(5);
exit(5);
}
int status = -1;
while(waitpid(-1, &status, WNOHANG) == 0){
printf("没有人打车, 继续等...\n");
sleep(1);
}
if(WIFEXITED(status)){
printf("这次等了%d分钟才拉到乘客(子进程退出码)\n", WEXITSTATUS(status));
printf("有人打车了, 出发~\n");
}
else{
printf("没有等到乘客(子进程异常退出)\n");
}
return 0;
}