Linux应用编程 | wait及waitpid

wait函数
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变量$?查看,因为Shell是它的父进程,当它终止时Shell调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。
父进程调用wait函数可以回收子进程终止信息。该函数有三个功能:
① 阻塞等待子进程退出
② 回收子进程残留资源
③ 获取子进程结束状态(退出原因)。
    pid_t wait(int *status); 成功:清理掉的子进程ID;失败:-1 (没有子进程),同时errno设为ECHILD
当进程终止时,操作系统的隐式回收机制会:1.关闭所有文件描述符 2. 释放用户空间分配的内存。内核的PCB仍存在。其中保存该进程的退出状态。(正常终止→退出值;异常终止→终止信号)
可使用wait函数传出参数status来保存进程的退出状态。如果我们对于子进程的退出状态并不在意的话,只是想回收该进程的资源,那直接往status传NULL即可(实际上我们大部分情况都不在意子进程退出状态)。如果想知道子进程退出状态的话,可借助宏函数来进一步判断进程终止的具体原因。宏函数可分为如下三组:
 1.  WIFEXITED(status) 为非0     → 进程正常结束
         WEXITSTATUS(status) 如上宏为真,使用此宏 → 获取进程退出状态 (exit的参数)
 2. WIFSIGNALED(status) 为非0 → 进程异常终止
         WTERMSIG(status) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。
*3. WIFSTOPPED(status) 为非0 → 进程处于暂停状态
         WSTOPSIG(status) 如上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。
         WIFCONTINUED(status) 为真 → 进程暂停后已经继续运行

例程1:
调用wait死等回收子进程,不关心子进程退出状态(status参数传NULL)。
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
	pid_t pid, wpid;
	
	pid = fork();
	if (pid == -1) {
		perror("fork");
		exit(1);
	} else if (pid == 0) {
		/* 子进程 */
		printf("I'm child process, pid = %d, going to exit in 3s\n", getpid());
		sleep(3);
	} else if (pid > 0) {
		/* 父进程 */
		printf("I'm parent process, going to wait the child process\n");
		/* 调用wait函数后,父进程将在此死等,直到子进程退出 */
		wpid = wait(NULL);	//如果不关心子进程的退出状态,只是想将子进程回收,那么status参数传NULL即可
		printf("I'm parent process, I catched child process: %d\n", wpid);
	}
	return 0;
}


例程2:
此例程我们用NORMALEXIT这个宏来控制子进程是否正常退出。如果NORMALEXIT为0的话,子进程通过execl调用一个abnormal函数,该函数将做除0操作,导致异常。
//wait2.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>

#define NORMALEXIT 0

int main()
{
	pid_t pid, wpid;
	int status;
	
	pid = fork();
	if (pid == -1) {
		perror("fork");
		exit(1);
	} else if (pid == 0) {
		/* 子进程 */
#if NORMALEXIT
		/* 子进程正常退出的情况 */
		printf("I'm child process, pid = %d, going to exit in 3s\n", getpid());
		sleep(3);
		exit(10);
#else
		/* 子进程异常退出的情况 */
		execl("./abnormal", "abnormal", NULL);
		perror("execl");
		exit(11);
#endif
	} else if (pid > 0) {
		/* 父进程 */
		printf("I'm parent process, going to wait the child process\n");
		wpid = wait(&status);	//使用status接收子进程的退出状态
		if (WIFEXITED(status)) {	//如果子进程正常退出,则WIFEXITED宏函数返回真
			printf("I'm parent process, child process: %d exit normally with return value: %d\n", 
				wpid, WEXITSTATUS(status));		//使用WEXITSTATUS获取进程退出状态(exit的参数)
		} else if (WIFSIGNALED(status)) {
			printf("I'm parent process, child process: %d is killed by signal: %d\n", 
				wpid, WTERMSIG(status));	//使用WTERMSIG获取使子进程终止的那个信号的编号
		}

	}
	return 0;
}


//abnormal.c
int main()
{
	int a = 1/0;
	return 22;
}
当子程序正常退出时:

    父进程抓到了子进程的退出参数:10。


当子程序异常退出时:
    父进程抓到了杀死子进程的信号:8(除零出错)。


waitpid函数
作用同wait,但可指定pid进程清理,可以不阻塞。
    pid_t waitpid(pid_t pid, int *status, in options);  成功:返回清理掉的子进程ID;失败:-1(无子进程)
特殊参数和返回情况:
参数pid:
> 0 回收指定ID的子进程  
-1 回收任意子进程(相当于wait)
0 回收和当前调用waitpid一个组的所有子进程
< -1 回收指定进程组内的任意子进程
返回0:参3为WNOHANG,且子进程正在运行。
参数options:目前只提供了两个选项:WNOHANG和WUNTRACED。如果不想使用的话,把它设为0即可。如果使用了WNOHANG参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。而WUNTRACED参数,由于涉及到一些跟踪调试方面的知识,用得极少。  
注意: 一次wait或waitpid调用只能清理一个子进程 ,清理多个子进程应使用循环。
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
	pid_t pid, wpid;
	
	pid = fork();
	if (pid == -1) {
		perror("fork");
		exit(1);
	} else if (pid == 0) {
		/* 子进程 */
		printf("I'm child process, pid = %d, going to exit in 3s\n", getpid());
		sleep(3);
	} else if (pid > 0) {
		/* 父进程 */
		printf("I'm parent process, going to wait the child process\n");
		do {
			wpid = waitpid(pid, NULL, WNOHANG);	//使用WNOHANG参数,不会阻塞,直接往下运行
			if (wpid ==0) {
				printf("no child process exit, wait 1s and try again\n");
				sleep(1);	//没有子进程退出,等待1秒再检测
			}
		} while (wpid == 0);
		
		if (wpid == pid) {
			printf("I'm parent process, I catched child process: %d\n", wpid);
		} else {
			printf("other situation...\n");
		}
	}
	return 0;
}

作者介绍
本人是一名Linux应用开发工程师,目前供职于一家世界500强公司,主要负责车联网产品的研发。喜交天下好友,欢迎关注本人公众号一起学习、交流!


猜你喜欢

转载自blog.csdn.net/yychuyu/article/details/80203521
今日推荐