Linux进程等待--wait()&waitpid()

1.进程等待

基本概念

进程退出时会关闭所有文件描述符,释放在用户空间分配的内存,但是PCB会暂时保留,里面存放退出状态。比如一个进程正常退出, PCB里就存放进程的退出状态(退出码);如果是异常退出,那么PCB里存放导致该进程终止的信号。子进程的退出状态应该是由父进程回收的,也就是父进程必须得等待子进程退出,接收子进程的退出状态。 如果子进程的退出状态父进程一直没有获取, 那么此时子进程就处于僵死状态,成为僵尸进程。

父进程为了拿到子进程的退出状态而等待子进程退出,目的是为了防止子进程变成僵尸进程。

进程等待的必要性

  • 如果父进程先于子进程退出,无法回收子进程的退出状态。那么子进程就成了孤儿进程,1号进程(init进程)会立即领养,但1号进程要想回收,还是得等子进程退出后再回收。
  • 如果子进程先于父进程退出,退出时为了保存退出状态,子进程的一些资源并没有释放。如果此时父进程并没有关注到子进程的退出(父进程是没有拿到子进程的退出状态), 子进程就会成为僵尸进程。僵尸进程会造成资源泄露,占用进程数的问题。

父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。

2.进程等待的方法

wait等待

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);

返回值:

  • 成功返回被等待进程的PID,失败则返回 -1。(成功指被等待进程正常退出, 失败指被等待子进程异常终止)

参数:
int* status:输出型参数,获取子进程退出状态。将其退出状态存到status所指向的内存空间,不关心被等待进程的退出状态则可以设置status为NULL

wait() 是阻塞的,返回之前阻塞等待子进程退出,再返回。

阻塞与非阻塞

  • 阻塞: 若发起调用,当前不具备完成条件,则一直等待;达到完成条件,调用才会结束

  • 非阻塞: 与阻塞相反,若发起调用,当前不具备完成条件,立即返回

最大区别是, 调用是否立即返回

waitpid等待

#include<sys/types.h>
#include<sys/wait.h>
pid_t waitpid(pid_t pid,int* status,int options);

返回值:

  • 当正常返回的时候waitpid返回收集到的子进程的进程ID;
  • 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0
  • 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

参数:
pid:

  • pid=-1 等待任一个子进程,与wait等效。
  • pid>0 等待其进程ID与pid相等的子进程。

status:

  • WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
  • WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

options: options = 1时,waitpid是非阻塞的;options = 其他值时waitpid() 是阻塞的

  • WNOHANG: 值为1的宏

详细来看:

  • 给 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,这时error会被设置成相应的值以指示错误所在

当waitpid是waitpid(-1, &status, 非1) 等效于 wait (&status)
不关心子进程退出码时waitpid(-1, NULL, 非1) 等效于 wait (NULL)

获取子进程status

wait/waitpid都可以获取到退出的子进程的退出状态,获取到的状态需要事先申请一块内存空间来保存,即 int* status 该参数是一个输出型参数,由操作系统填充。如果传递NULL,表示不关心子进程的退出状态;否则操作系统会根据该参数,将子进程的退出信息反馈给父进程。

tatus不能简单的当作整形来看待,可以当作位图来看待,只研究status低16比特位:
在这里插入图片描述
在正常退出时,低16位中的高8位存的是子进程的退出码,后8位全为0;在异常退出时,低7位存的是进程的异常退出信号值,第8位是core dump 标志(核心转储文件标志),目的是为了方便用户查看异常终止的信息,此时低16位中的高8位未用。所以,在wait/waitpid结束后,要先取出status的低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");
}

3.模拟实现阻塞等待和非阻塞等待

  • 用wait来模拟阻塞等待:快递员会一直等待用户来取快递,直到快递站下班。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

int main(){
	printf("快递员等待客户来取快递\n");
	pid_t pid=fork();
	if(pid<0){
		perror("fork error");
		exit(-1);
	}else if(pid==0){
		sleep(15);
		exit(15);
	}
	int status=-1;
	wait(&status);
	if(!(status&0x7f)){
		printf("%d分钟后用户取走了快递\n",(status>>8)&0xff);
	}
	else {
		printf("快递站下班了\n");                                                                                                                            
	}
	return 0;
}

子进程正常退出,即快递员等待到客户来去取走快递:
在这里插入图片描述
在另一个窗口下用kill -9 杀死子进程,此时子进程异常终止, 结果如下:
在这里插入图片描述

  • 用waitpid模拟非阻塞等待:出租车司机在车站等待乘客的场景,出租车司机会等待乘客;当长的时间拉不到人,就离开去别的地方拉乘客
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(){
  printf("出租车司机在车站等待乘客\n");
  pid_t pid=fork();
  if(pid<0){
    perror("fork error");
    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;
}

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值