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强公司,主要负责车联网产品的研发。喜交天下好友,欢迎关注本人公众号一起学习、交流!