目录
背景
产生
处理方法?
方法一:父进程通过wait或者wait_pid方式回收子进程
方法二:信号处理signal
改进版
背景
????????父进程创建子进程之后,父进程没有等待该子进程的退出,子进程就会成为僵尸进程,如果父进程也退出,这个时候子进程也可以被init进程回收,释放资源。如果父进程不退出,子进程占用的资源将永远不会被释放。
产生
以下示例是一个产生僵尸进程的典型例子
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char **argv) {
pid_t pid = fork();
if(pid > 0){
printf("i am parent process PID=%d\n", getpid());
sleep(10);
}else if(pid == 0) {
printf("i am child process PID=%d\n", getpid());
return 0;
}else{
printf("fork error\n");
return -1;
}
return 0;
}
运行结果:
$ ./test i am parent process PID=1742 i am child process PID=1743
查看进程:
$ ps -ef | grep test pi 1742 1040 0 19:19 pts/0 00:00:00 ./test pi 1743 1742 0 19:19 pts/0 00:00:00 [test] <defunct> pi 1746 1040 0 19:19 pts/0 00:00:00 grep --color=auto test
当主进程十秒运行结束之后
[1]+ 已完成 ./test $ ps -ef | grep test pi 1750 1040 0 19:19 pts/0 00:00:00 grep --color=auto test
????????可以看到有“已完成”字样的打印,但是再次查看进程列表的时候发现,僵尸进程[test] <default>已经不存在了,从以上的打印可以看到,开始主进程运行进行sleep等待,子进程运行结束之后立马退出,产生僵尸进程;等到父进程1742结束之后,僵尸进程1743被init进程回收。
处理方法?
????????那么如何消除这种子进程退出后成为僵尸进程的问题呢?
方法一:父进程通过wait或者wait_pid方式回收子进程
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
int main(int argc, char **argv) {
pid_t pid = fork();
int status, i;
if(pid > 0){
printf("i am parent process PID=%d\n", getpid());
//wait(NULL);
wait(&status);
i = WEXITSTATUS(status);
printf("child process done! status=%d\n", i);
//sleep(10);
}else if(pid == 0) {
printf("i am child process PID=%d\n", getpid());
sleep(5);
return 0;
}else{
printf("fork error\n");
return -1;
}
return 0;
}
运行结果
i am parent process PID=1841 i am child process PID=1842 child process done! status=0
????????可以看到产生子进程之后打印 printf("child process done!\n");被wait阻塞,说明wait是阻塞型的,同样我们通过ps -ef查看进程列表,可以发现没有出现僵尸进程。当我们不关心子进程状态时候,这里wait的参数可以设定为NULL。
????????watipid(pid_t pid, int *status, int options)是在wait(int *status)的基础上增加了一些个性化设定,可以监听一组、或者指定的子进程pid,具体使用如下:
参数pid pid<-1 等待进程组识别码为pid绝对值的任何子进程。 pid=-1 等待任何子进程,相当于wait()。 pid=0 等待进程组识别码与目前进程相同的任何子进程。 pid>0 等待任何子进程识别码为pid的子进程。
参数options 参数option可以为0 或下面的OR 组合 WNOHANG 如果没有任何已经结束的子进程则马上返回,不予以等待。 WUNTRACED 如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会。
参数status返回值 WIFEXITED(status)如果子进程正常结束则为非0值。 WEXITSTATUS(status)取得子进程exit()返回的结束代码,一般会先用WIFEXITED 来判断是否正常结束才能使用此宏。 WIFSIGNALED(status)如果子进程是因为信号而结束则此宏值为真 WTERMSIG(status)取得子进程因信号而中止的信号代码,一般会先用WIFSIGNALED 来判断后才使用此宏。 WIFSTOPPED(status)如果子进程处于暂停执行情况则此宏值为真。一般只有使用WUNTRACED 时才会有此情况。 WSTOPSIG(status)取得引发子进程暂停的信号代码,一般会先用WIFSTOPPED 来判断后才使用此宏。
waitpid返回值 如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno中
????????上面这个会有一个问题,调用wait的父进程会被一直阻塞,它无法继续执行后面的任务,直到子进程退出之后。那么针对这种情况如何处理呢?可以将wait用信号来代替
方法二:信号处理signal
void (*signal(int signum,void(* handler)(int)))(int);
函数说明: signal()会依参数signum 指定的信号编号来设置该信号的处理函数。当指定的信号到达时就会跳转到参数handler指定的函数执行。如果参数handler不是函数指针,则必须是下列两个常数之一: SIG_IGN 忽略参数signum指定的信号。 SIG_DFL 将参数signum 指定的信号重设为核心预设的信号处理方式。
????????子进程退出的时候内核会发送SIGCHLD给父进程,所以父进程可以监听这个信号,并设定信号处理函数,上面的示例可以改进如下:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>
void childProcess(){
int round = 10;
while(round > 0){
printf("round=%d\n", round);
sleep(1);
round--;
}
}
void sig_handler(int signum){
pid_t pid;
if(signum == SIGCHLD){
printf("get child exit signal\n");
while((pid = waitpid(-1, NULL, WNOHANG)) > 0){
printf("child %d exit\n", pid);
}
}
}
int main(int argc, char **argv) {
pid_t pid = fork();
//int status, i;
signal(SIGCHLD, sig_handler);
int tmp = 10;
if(pid > 0){
printf("i am parent process PID=%d\n", getpid());
//wait(NULL);
//wait(&status);
//i = WEXITSTATUS(status);
//printf("child process done! status=%d\n", i);
sleep(5);
printf("do parent`s thing\n");
while(tmp > 0){
sleep(1);
printf("parent loop =%d\n", tmp);
tmp--;
}
printf("parent exit\n");
return 0;
}else if(pid == 0) {
printf("i am child process PID=%d\n", getpid());
//sleep(5);
childProcess();
return 0;
}else{
printf("fork error\n");
return -1;
}
return 0;
}
运行效果
./test i am parent process PID=2014 i am child process PID=2015 round=10 round=9 round=8 round=7 round=6 do parent`s thing round=5 parent loop =10 round=4 parent loop =9 round=3 parent loop =8 round=2 parent loop =7 round=1 parent loop =6 get child exit signal child 2015 exit parent loop =5 parent loop =4 parent loop =3 parent loop =2 parent loop =1 parent exit
????????这里需要注意,我们用的是waitpid方式来处理子进程回收的监听,如果系统繁忙时,有两个子进程同时结束,这时只会发送一个SIGCHLD信号,如果只wait一次,也会产生僵尸进程,所以这里使用waitpid加带入WNOHANG参数,使得调用的时候立马返回,根据返回值判断等待结果。
改进版
当然比较简单的应用,我们可以直接用wait(NULL)的方式,它等价于waitpid(-1, NULL, 0),具体可以看上面关于waitpid参数说明。
当然我们也可以给signal的第二个参数直接输入一个常数,也可以达到回收效果
signal(SIGCHLD, SIG_IGN);
以上表示直接忽略SIGCHLD信号,让父进程不必关心子进程退出状态,直接清理。
如果参数handler不是函数指针,则必须是下列两个常数之一: SIG_IGN 忽略参数signum指定的信号。 SIG_DFL 将参数signum 指定的信号重设为核心预设的信号处理方式。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>
void childProcess(){
int round = 10;
while(round > 0){
printf("round=%d\n", round);
sleep(1);
round--;
}
}
void sig_handler(int signum){
pid_t pid;
if(signum == SIGCHLD){
printf("get child exit signal\n");
while((pid = waitpid(-1, NULL, WNOHANG)) > 0){
printf("child %d exit\n", pid);
}
}
}
int main(int argc, char **argv) {
pid_t pid = fork();
//int status, i;
//signal(SIGCHLD, sig_handler);
signal(SIGCHLD, SIG_IGN);
int tmp = 10;
if(pid > 0){
printf("i am parent process PID=%d\n", getpid());
//wait(NULL);
//wait(&status);
//i = WEXITSTATUS(status);
//printf("child process done! status=%d\n", i);
sleep(5);
printf("do parent`s thing\n");
while(tmp > 0){
sleep(1);
printf("parent loop =%d\n", tmp);
tmp--;
}
printf("parent exit\n");
return 0;
}else if(pid == 0) {
printf("i am child process PID=%d\n", getpid());
//sleep(5);
childProcess();
return 0;
}else{
printf("fork error\n");
return -1;
}
return 0;
}
i am parent process PID=2047 i am child process PID=2048 round=10 round=9 round=8 round=7 round=6 do parent`s thing round=5 parent loop =10 round=4 parent loop =9 round=3 parent loop =8 round=2 parent loop =7 round=1 parent loop =6 parent loop =5 parent loop =4 parent loop =3 parent loop =2 parent loop =1 parent exit
从打印看,子进程退出之后,父进程并没有做特殊处理,直接回收(可以在子进程退出执行完之后,ps查看是否有对应子进程pid)。
|