标签:https 产生 循环 严格 exit break 数据结构 tab 系统资源
1、概念
1、孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,从而保证每个进程都会有一个父进程。而Init进程会自动wait其子进程,因此被Init接管的所有进程都不会变成僵尸进程。
补充:孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上。每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程结束了其生命周期的时候,init进程就会处理它的一切善后工作。因此孤儿进程并不会有什么危害。
2、僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,这种进程称之为僵尸进程。当用ps命令观察进程的执行状态时,看到这些进程的状态栏为defunct。僵尸进程是一个早已死亡的进程,但在进程表(processs table)中仍占了一个位置(slot)。
补充(内核):一个进程终止后,内核会释放终止进程(调用了exit系统调用)所使用的所有存储区,关闭所有打开的文件等。但内核为每一个终止子进程保存了一定量的信息,设置僵死状态来维护子进程的信息,以便父进程在以后某个时候获取,这些信息至少包括进程ID,进程的终止状态,以及该进程使用的CPU时间,所以当终止子进程的父进程调用wait或waitpid时就可以得到这些信息。任何一个子进程(init除外)在exit后并非马上就消失,而是留下一个称外僵尸进程的数据结构,等待父进程处理。这是每个子进程都必需经历的阶段。另外子进程退出的时候会向其父进程发送一个SIGCHLD信号。
严格来说,僵尸进程并不是问题的根源,罪魁祸首是产生出大量僵尸进程的那个父进程。因此,把产生大量僵尸进程的那个父进程kill掉(通过kill发送SIGTERM或者SIGKILL信号)之后,它产生的僵死进程就变成了孤儿进程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源。
2、如何避免僵尸进程?
1、通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收。如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。
2、父进程调用wait/waitpid等函数等待子进程结束,如果尚无子进程退出wait会导致父进程阻塞。waitpid可以通过传递WNOHANG使父进程不阻塞立即返回。
3、如果父进程很忙可以用signal注册信号处理函数,在信号处理函数调用wait/waitpid等待子进程退出。
4、通过两次调用fork。父进程首先调用fork创建一个子进程然后waitpid等待子进程退出,子进程再fork一个孙进程后退出。这样子进程退出后会被父进程等待回收,而对于孙子进程其父进程已经退出所以孙进程成为一个孤儿进程,孤儿进程由init进程接管,孙进程结束后,init会等待回收。
第一种方法忽略SIGCHLD信号,这常用于并发服务器的一个技巧,因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理方式设为忽略,可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。
3、实验
以下实验了多种情况,用于理解父进程wait多个子进程
#include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> int main() { pid_t pid; if(fork() == 0) { printf("1--This is the child process. pid =%d\n", getpid()); sleep(3); printf("%d exit\n", getpid()); exit(0); } if(fork() == 0) { printf("2--This is the child process. pid =%d\n", getpid()); sleep(10); printf("%d exit\n", getpid()); exit(0); } if(fork() == 0) { printf("3--This is the child process. pid =%d\n", getpid()); sleep(17); printf("%d exit\n", getpid()); exit(0); } // while(1) // { // pid = wait(NULL); // printf("parent:%d,return of wait:%d\n", getpid(), pid); // if(pid == -1) // { // break; // } // } //循环wait每一个子进程输出,最后返回值是-1表示没有子进程了,该父进程也退出 //while(wait(NULL) != -1); //wait最后一个子进程退出后,该父进程退出 //printf("parent:%d,return of wait:%d\n", getpid(), wait(NULL)); //wait第一个返回的进程后,该父进程就返回,其他未返回进程变孤儿进程,由init进程接管 // while(wait(NULL) != -1) // { // printf("parent:%d,return of wait:%d\n", getpid(), wait(NULL)); // } //while里wait第一个退出的子进程,printf里wait第二个子进程输出,while再wait第三个子进程,最后printf里是-1,表明已经没有子进程,父进程退出 signal(SIGCHLD,SIG_IGN);//忽略子进程exit,内核直接转交init处理defunct,这样就算父进程没退出也不会有僵尸进程 sleep(20);//父进程没有wait任何子进程,没有设置SIG_IGN,且还在运行中时子进程就退出了,此时所有子进程变僵尸进程,直到父进程退出后,子进程被init进程接管处理 printf("exit\n"); return 0; }
总结:
调用一个wait,则第一个子进程返回后该父进程也返回,那其他子进程还在运行,为何没有变成僵尸进程,而是直接由init接管
-----父进程返回了,所以其他子进程变孤儿进程,直接由init接管,所以ps –ef | grep defunct没有僵尸进程
如果父进程fork多个子进程,子进程退出后,该父进程还在运行中,但没wait任何子进程,则此时的子进程会变僵尸进程,直到父进程结束,然后由init进程接管
-----父进程不wait阻塞,而且在子进程结束后还在运行。此时为了不产生僵尸进程,可以在父进程中设置signal(SIGCHLD,SIG_IGN); 使得父进程忽略子进程退出,把子进程直接交由init接管
概念参考:
https://www.cnblogs.com/wuchanming/p/4020463.html
https://www.cnblogs.com/Anker/p/3271773.html
标签:https 产生 循环 严格 exit break 数据结构 tab 系统资源
原文地址:https://www.cnblogs.com/beixiaobei/p/9064815.html