一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的 PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用 wait 或 waitpid 获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在 Shell 中用特殊变量$?查看,因为 Shell 是它的父进程,当它终止时 Shell 调用 wait 或 waitpid 得到它的退出状态同时彻底清除掉这个进程。
wait函数原型及作用
作用:
1、阻塞等待子进程退出
2、回收子进程残留资源
3、获取子进程结束状态(退出原因)。
原型:
pid_t wait (int *status);
成功:清理掉的子进程 ID;失败:-1 (没有子进程)
宏函数判断终止原因
WIFEXITED(status)宏判断为真 ---> 表示程序正常退出
WEXITSTATUS(status) 上一个宏判断为真 则返回状态值
WIFSIGNALED(status) 宏判断为真 ---> 表示程序异常退出
WTERMSIG(status) 上一个判断为真,则返回状态值
wait和宏函数配套使用实例
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t pid,wpid;
int status;
pid =fork();
if(pid==0)
{
printf(" i am child,my id is%d\n",getpid());
printf("child die\n");
return 73;
}
else if(pid>0)
{
wpid=wait(NULL);//不关心怎么结束的
wpid = wait(&status);//等待子进程结束
if(wpid==-1)
{
perror("wait error");
exit(1);
}
if(WIFEXITED(status))//判断 子进程正常退出判断
{
printf("child exit with%d\n",WEXITSTATUS(status));
printf("------parent finish\n");
}
if(WIFSIGNALED(status))//判断 子进程异常退出判断
{
printf("child exit with%d\n",WTERMSIG(status));
}
}
else
{
perror("fork");
return 1;
}
}
waitpid函数原型及作用
作用:
作用同 wait,但可指定 pid 进程清理,可以不阻塞。
原型:
pid_t waitpid ( pid_t pid, int *status, in options );
成功:返回清理掉的子进程 ID; 失败:-1(无子进程)
参数:
pid : 指定回收的子进程pid
status : (传出) 回收进程的状态
options : WNOHANG 指定回收方式为 ---> 非阻塞
等待集合的成员是由参数 pid 来确定
pid : > 0 回收指定ID的子进程
-1 回收任意子进程(相当于wait)
0 回收和当前调用waitpid一个组的所有子进程
< -1 回收指定进程组内的任意子进程
返回值:
>0 如果成功,则为子进程的PID,
0 : 如果options为WNOHANG,则返回0,且子进程正在运行
-1: 如果发生其他错误,则返回-1
注意:
一次 wait 或 waitpid 调用只能清理一个子进程,清理多个子进程应使用循环。
二者等价 waitpid (-1, &status, 0 ) == wait ( &status )
实例一:waitpid回收指定子进程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
int main(int argc, char *argv[])
{
int i;
pid_t pid, wpid, tmpid;
for (i = 0; i < 5; i++) {
pid = fork(); // for循环在父进程中执行 父进程返回子进程的ID 子进程返回 0
if (pid == 0) { // pid=0,子进程退出 循环期间, 子进程不 fork
break;
}
if (i == 2) {
tmpid = pid; //子进程pid
printf("--------pid = %d\n", tmpid);
}
}
if (5 == i) { // 父进程, 从 表达式 2 跳出
// sleep(5);
//wait(NULL); // 一次wait/waitpid函数调用,只能回收一个子进程.
//wpid = waitpid(-1, NULL, WNOHANG); //回收任意子进程,没有结束的子进程,父进程直接返回0
//wpid = waitpid(tmpid, NULL, 0); //指定一个进程回收, 阻塞等待
printf("i am parent , before waitpid, pid = %d\n", tmpid);
//wpid = waitpid(tmpid, NULL, WNOHANG); //指定一个进程回收, 不阻塞
wpid = waitpid(tmpid, NULL, 0); //指定一个进程回收, 阻塞回收
if (wpid == -1) {
perror("waitpid error");
exit(1);
}
printf("I'm parent, wait a child finish : %d \n", wpid);
} else { // 子进程, 从 break 跳出
sleep(i);
printf("I'm %dth child, pid= %d\n", i+1, getpid());
}
return 0;
}
实例二:waitpid回收指定子进程(错误写法)
这段代码从结果上可以看出做不到回收指定的进程。两段代码的区别在于子进程id的存放的方式。
在这段代码中,我们在子进程中做了pid = getpid();父子进程资源不共享导致的是父进程不知道这个pid的值。
而在上一段代码中,我们直接通过 pid = fork();tmpid = pid;这样的方式在父进程中成功保存子进程的进程号。
//指定回收一个子进程错误示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
int main(int argc, char *argv[])
{
int i;
pid_t pid, wpid;
for (i = 0; i < 5; i++) {
if (fork() == 0) { // 循环期间, 子进程不 fork
if (i == 2) {
pid = getpid(); // 保存在子进程
printf("------pid = %d\n", pid);
}
break;
}
}
if (5 == i) { // 父进程, 从 表达式 2 跳出
sleep(5);
printf("------in parent , before waitpid, pid= %d\n", pid);
wpid = waitpid(pid, NULL, 0); //指定一个进程回收
if (wpid == -1) {
perror("waitpid error");
exit(1);
}
printf("I'm parent, wait a child finish : %d \n", wpid);
} else { // 子进程, 从 break 跳出
sleep(i);
printf("I'm %dth child, pid= %d\n", i+1, getpid());//用户空间释放,内核PCB残留
}
return 0;
}
实例三:waitpid 回收全部子进程
// 回收多个子进程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
int main(int argc, char *argv[])
{
int i;
pid_t pid, wpid;
for (i = 0; i < 5; i++) {
pid = fork();
if (pid == 0) { // 循环期间, 子进程不 fork
break;
}
}
if (5 == i) { // 父进程, 从 表达式 2 跳出
while((wpid=waitpid(-1,NULL,0))>0)
{
printf("I'm parent,wait a child finish:%d\n",wpid);
}
} else { // 子进程, 从 break 跳出
sleep(i);
printf("I'm %dth child, pid= %d\n", i+1, getpid());
}
return 0;
}
暴力回收子进程
kill -9 父进程号
查看 kill -l