1.孤儿进程
父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程
2.僵尸进程
子进程终止,父进程尚未回收,子进程残留资源(PCB)存放在内核中,变成僵尸(zombie)进程
特别注意:僵尸进程是不能使用kill命令清除掉的。以为kill命令只能终止活着的进程,而僵尸进程已经终止。
3.回收僵尸进程
模拟一个僵尸进程:zombie.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> int main(void) { pid_t pid; pid = fork(); if(pid == 0) { printf("----child, going to sleep,my parent=%d ", getppid()); sleep(10); printf("----child die---- "); } else if(pid > 0) { while(1){ printf("I am parent, pid=%d, myson=%d ", getpid(), pid); sleep(1); } } else { perror("fork error"); exit(1); } return 0; }
ps aux 查询结果如下:
root 3230 0.0 0.0 4216 352 pts/0 S+ 17:36 0:00 ./zombie.out
root 3231 0.0 0.0 0 0 pts/0 Z+ 17:36 0:00 [zombie.out] <defunct>--僵尸进程
wait函数
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:
如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用
wait或者waitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态我们可以在shell中用特殊变量$?
查看,因为shell是它的父进程,当它终止时shell调用wait或waitpid得到它的退出状态同时彻底清除这个进程。
父进程调用wait函数可以回收子进程终止信息,该函数有三个功能:
1).阻塞等待子进程退出
2).回收子进程残留资源
3).获取子进程结束状态(退出原因)
pid_t wait(int *status);
成功:清理掉子进程ID;
失败:-1(没有子进程)
当进程终止时,操作系统的隐式回收机制会做如下操作:
1).关闭所有的文件描述符
2).释放用户空间分配的内存
内核的PCB仍存在,其中保存该进程的退出状态。(正常终止-->退出值,异常终止-->终止信号)
可使用wait函数传出参数status来保存进程的退出状态。借助宏函数来进一步判断进程终止的具体原因。
宏函数可分为三组:
1).WIFEXITED(status) 为非0 -->进程正常结束
WEXITSTATUS(status) 如上宏为真,使用此宏-->获取进程退出状态(exit的参数)
2).WIFSIGNALED(status) 为非0 -->进程异常终止
WTERMSIG(status) 如上宏为真,使用此宏-->取得进程终止的那个信号的编号(kill的参数)
wait第一版:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> int main(void) { pid_t pid, wpid; pid = fork(); if(pid == 0) { printf("----child, going to sleep,my parent=%d ", getppid()); sleep(10); printf("----child die---- "); } else if(pid > 0) { wpid = wait(NULL); if(wpid == -1){ perror("wait error"); exit(1); } while(1){ printf("I am parent, pid=%d, myson=%d ", getpid(), pid); sleep(1); } } else { perror("fork error"); exit(1); } return 0; }
wait使用宏版:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> int main(void) { pid_t pid, wpid; int status; pid = fork(); if(pid == 0) { printf("----child, going to sleep,my parent=%d ", getppid()); sleep(20); printf("----child die---- "); return 100; } else if(pid > 0) { wpid = wait(&status); if(wpid == -1){ perror("wait error"); exit(1); } if(WIFEXITED(status)) { printf("child exit with %d ",WEXITSTATUS(status)); } if(WIFSIGNALED(status)) { printf("child killed by %d ",WTERMSIG(status)); } while(1){ printf("I am parent, pid=%d, myson=%d ", getpid(), pid); sleep(1); } } else { perror("fork error"); exit(1); } return 0; }
gcc abnor.c -o abnor.out 测试段错误和浮点错误
int main(void){ char *p = "test of wait abnormally "; //p[0] = 'h';//测试段错误 int a = 5/0;//测试浮点错误 return 65;
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> int main(void) { pid_t pid, wpid; int status; pid = fork(); if(pid == 0) { execl("abnor.out", "abnor.out", NULL); printf("----child, going to sleep,my parent=%d ", getppid()); sleep(20); printf("----child die---- "); return 100; } else if(pid > 0) { wpid = wait(&status); if(wpid == -1){ perror("wait error"); exit(1); } if(WIFEXITED(status)) { printf("child exit with %d ",WEXITSTATUS(status)); } if(WIFSIGNALED(status)) { printf("child killed by %d ",WTERMSIG(status)); } while(1){ printf("I am parent, pid=%d, myson=%d ", getpid(), pid); sleep(1); } } else { perror("fork error"); exit(1); } return 0; }
根据kill -l找到对应的信号
waitpid函数
可以指定pid进程清理,可以不阻塞。
pid_t waitpid(pid_t pid, int *status, int options);
成功:返回清理掉的子进程ID; 失败:-1(无子进程结束)
特殊参数和返回值情况:
参数1 pid:
>0 回收指定ID的子进程
-1 回收任意子进程(相当于wait)
0 回收和当前调用waitpid一个组的所有子进程
<-1回收指定进程组内的任意子进程
参数2 status
参数3 设置为WNOHANG,非阻塞回收(轮询)
设置为0,父进程阻塞等待子进程执行完,阻塞回收
返回值: 成功:pid
失败:-1
返回0:参数3设置为WNOHANG,且子进程正在运行
注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。