系统编程-回收子进程-孤儿和僵尸进程,wait和waitpid方法
1. 孤儿进程
孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程。
危害:没什么危害…
产生孤儿进程的举例:
#include <stdio.h>
#include <unistd.h>
int main(){
pid_t pid = fork();
if(pid == 0){// child
while(1){//强制不让子进程退出,变为孤儿进程
printf("I am child,PID:%d,PPID:%d\n",pid,getpid(),getppid());
sleep(1);
}
}else if(pid>0){// parent
printf("I am parent,PID:%d,PPID:%d\n",pid,getpid(),getppid());
sleep(5);
printf("I am parent,I will die!\n");
}
return 0;
}
2. 僵尸进程
僵尸进程: 子进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,此子进程便成为僵尸(zombie)进程。
注意:僵尸进程是不能使用kill命令清除掉的。因为kill命令只是用来终止进程的,而僵尸进程已经终止。
产生僵尸进程的举例:
#include <stdio.h>
#include <unistd.h>
int main(){
pid_t pid = fork();
if(pid == 0){// child
printf("I am child,PID:%d,PPID:%d\n",pid,getpid(),getppid());
sleep(2);
printf("I am child,I will die!\n");
}
}else if(pid>0){// parent
while(1){//强制不让父进程退出,子进程变为僵尸进程
printf("I am parent,PID:%d,PPID:%d\n",pid,getpid(),getppid());
sleep(5);
}
}
return 0;
}
危害:僵尸进程会占用系统资源,如果很多,则会严重影响服务器的性能;
如何解决僵尸进程:
①杀死他的父进程使其变成孤儿进程,进而被系统处理。
②wait函数
3. 子进程回收
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait
或waitpid
获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变量$?
查看,因为Shell是它的父进程,当它终止时Shell调用wait
或waitpid
得到它的退出状态同时彻底清除掉这个进程。
3.1 wait
父进程调用wait
函数可以回收子进程终止信息。该函数有三个功能:
① 阻塞等待子进程退出
② 回收子进程残留资源
③ 获取子进程结束状态(退出原因)。
wait
函数的原型是:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
进程一旦调用了wait
,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸进程的子进程,wait
就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait
就会一直阻塞在这里,直到有一个这样的进程出现为止。
参数status
用来保存被回收进程退出时的一些状态,如果我们不想知道这个子进程是如何死掉的,只想把它消灭掉的话,那么我们可以设定这个参数为NULL
,就像下面这样:
pid_t pid = wait(NULL);
如果成功,wait
会返回被回收子进程的进程ID
,如果调用进程没有子进程,调用就会失败,此时wait
返回-1
,同时errno
被置为ECHILD
。
返回的status
只是一个整型变量,不能很精确的描述出状态,因此需要借助宏函数来进一步判断进程终止的具体原因。经常用到的宏函数为如下两组:
//正常死亡WIFEXITED
if(WIFEXITED(status)){
pfintf("%d:"WEXITSTATUS(status));
}
//非正常死亡
if(WIFSIGNALED(status)){
pfintf("%d:"WTERMSIG(status));
}
WIFEXITED(status)
为非0 → 进程正常结束
WEXITSTATUS(status)
如上宏为真,使用此宏 → 获取进程退出状态 (return/exit
的参数)
WIFSIGNALED(status)
为非0 → 进程异常终止
WTERMSIG(status)
如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(){
pid_t pid = fork();
if(pid==0){
printf("I am child,will die!\n");
sleep(2);
//while(1){
printf("I am child,Come and hit me!!\n");
sleep(1);
//}
return 101;
}
else if(pid>0){
printf("I am parent,wait for child die\n");
int status;
pid_t wpid = wait(&status);
printf("wait ok,wpid = %d,pid = %d\n",wpid,pid);
//正常死亡WIFEXITED
if(WIFEXITED(status)){
printf("child exit with %d\n:",WEXITSTATUS(status));
}
//非正常死亡
if(WIFSIGNALED(status)){
printf("child killed by %d:",WTERMSIG(status));
}
}
}
子进程正常死亡运行结果:
子进程一直运行时(while(1)
),kill
掉之后运行结果:
3.2 waitpid
作用同wait
,但可指定pid进程清理,可以不阻塞。
waitpid
函数的原型是:
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
返回值:
成功:返回清理掉的子进程ID
;
失败:-1
(出错,无子进程);
返回0
值,参3传WNOHANG
,且子进程正在运行。
pid
:
从参数的名字pid
和类型pid_t
就可以看出,这里需要的是一个进程ID。当pid
取不同的值时,在这里有不同的意义。
- pid >0时,回收指定ID的子进程,只等待进程ID等于
pid
的子进程,不管其他已经有多少个子进程运行结束退出了,只要指定的子进程还没结束,waitpid
就会一直等下去; - pid = -1时,回收任意子进程(相当于
wait
),等待任何一个子进程退出,没有 任何限制; - pid = 0时,回收和当前调用waitpid一个组的所有子进程,如果 子进程已经加入了别的进程组,
waitpid
不会对它做任何理睬; - pid < -1时,回收指定进程组内的任意子进程,这个进程组的ID等于
pid
的绝对值;
options
:
- 0:(相当于wait)阻塞回收
WNOHANG
:非阻塞回收,用轮询结构回收。
3. wait和waitpid的区别
- 在一个子进程终止前,
wait
使其调用者阻塞,而waitpid
则提供了非阻塞版本; waitpid
等待一个指定的子进程,而wait
等待第一个终止的子进程;waitpid
支持作业控制(以WUNTRACED
选项,由pid
指定的任一子进程状态,且其状态自暂停以来还未报告过,则返回其状态);
注意:一次wait
或waitpid
调用只能清理一个子进程,清理多个子进程应使用循环。
wait
循环回收:
运行结果:
waitpid循环回收:
运行结果: