孤儿/僵尸进程——回收子进程
参考博客:https://blog.csdn.net/qq_35396127/article/details/78725915
:https://www.cnblogs.com/Anker/p/3271773.html
在Linux下,子进程可由父进程创建,子进程也可以创建新的进程。但是父进程无法预测子进程的运行状态,不知道子进程何时会结束。由此会产生孤儿进程与僵尸进程。所以当一个进程结束后,它的父进程需要调用wait(),waitpid()系统调用获取子进程终止状态,回收子进程。
什么是孤儿进程与僵尸进程?
孤儿进程:
父进程先于子进程结束,则子进程失去父进程,子进程就会被init 进程收养,子进程就成为孤儿进程。
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 5 int main(void) 6 { 7 pid_t pid; 8 int i=0; 9 //创建一个子进程 10 pid = fork(); 11 12 if(pid == -1) 13 { 14 perror("fork"); 15 exit(1); //1:异常退出 0:正常退出 16 } 17 else if(pid>0) 18 { 19 printf("I am parent, my pid=%d ",getpid()); 20 sleep(4); //父进程运行4秒后结束 21 printf("---------parent going to die----------- "); 22 23 } 24 else 25 { 26 while(i<80) 27 { 28 //待父进程结束后会被init收养 29 printf("I am child, pid = %d, parentpid = %d ",getpid(),getppid()); 30 sleep(1); 31 i++; 32 } 33 } 34 return 0; 35 }
结果:
有的Ubuntu版本,会设置user init 进程专门处理孤儿进程
僵尸进程:
子进程终止,父进程未回收子进程的资源PCB,使其变成僵尸进程。
测试程序:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <errno.h> 4 #include <stdlib.h> 5 6 int main(){ 7 8 pid_t pid; 9 pid = fork(); 10 if(pid < 0) 11 { 12 perror("fork error:"); 13 exit(1); 14 } 15 else if (pid == 0) 16 { 17 printf("I am child, I am exiting. "); 18 exit(0); 19 } 20 21 printf("I am parent,I will sleep 2s "); 22 //等待子进程先退出 23 sleep(2); 24 //输出进程信息 25 system("ps -o pid,ppid,state,tty,command"); 26 printf("father process exiting "); 27 28 return 0; 29 } 30 31 源自:https://www.cnblogs.com/Anker/p/3271773.html
结果:
孤儿进程与僵尸进程的危害
在Linux中,每个进程退出时,内核会释放该进程所有资源,包括打开的文件,占用的内存等,但仍为之保留一定信息,包括:进程ID,退出状态,运行时间等。直到父进程通过wait/waitpid来取时,才会释放。因此,只要进程一直调用wait与waitpid,进程占用的资源就不会释放,进程号也不会释放,由于系统能使用的进程号是有限的,就可能因为大量僵尸进程占用进程号而不能产生新进程。当系统中产生大量僵尸进程时,应该把产生僵尸进程的父进程给杀死掉。可以通过kill发送SIGTERM或者SIGKILL信号,之后僵尸进程会因为没了父进程变成孤儿,被init收养再释放。
对于孤儿进程,会被init进程收养,而且init进程会循环地wait()它收养的子进程。所以孤儿进程并无危害。
通过信号机制解决僵尸进程
子进程退出时会向父进程发送SIGCHLD信号,父进程调用信号处理函数,进而调用wait处理僵尸进程。测试程序:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <errno.h> 4 #include <stdlib.h> 5 #include <signal.h> 6 7 static void handlefunc(int sig) 8 { 9 pid_t pid; 10 int stat; 11 12 //处理僵尸进程 13 //waitpid(-1:回收任一子进程,子进程结束状态,不阻塞父进程) 14 //waitpid成功返回子进程pid 15 while((pid = waitpid(-1, &stat, WNOHANG))>0) 16 printf("child %d terminated. ",pid); 17 } 18 19 int main() 20 { 21 pid_t pid; 22 //创建signal,捕捉子进程退出信号 23 signal(SIGCHLD,handlefunc); 24 pid = fork(); 25 if(pid<0) 26 { 27 perror("fork error:"); 28 exit(1); 29 } 30 else if(pid == 0) 31 { 32 printf("I am chid, pid=%d. I exiting ",getpid()); 33 exit(0); 34 } 35 printf("I am parent. I sleep 3S "); 36 //等待子进程退出 37 sleep(3); 38 //输出进程信息 39 system("ps -o pid,ppid,state,tty,command"); 40 printf("parent exiting"); 41 42 return 0; 43 44 }
结果:僵尸进程消失
signal(SIGCLD,SIG_IGN);
因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理方式设为忽略,可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。
详见:https://blog.csdn.net/u012317833/article/details/39253793