 孤兒進程和僵尸進程
							時間(jian):2018-08-15      來源(yuan):未(wei)知(zhi)
							孤兒進程和僵尸進程
							時間(jian):2018-08-15      來源(yuan):未(wei)知(zhi) 
							前段時間,由于(yu)研(yan)究經典面試題,把孤兒(er)進程和僵尸進程也總結了一下。
我們(men)有這樣一個問(wen)題:孤兒進程和(he)僵(jiang)尸進程,怎么產(chan)生的?有什么危害?怎么去預防(fang)?
下面是針對此問題(ti)的總結(jie)與概括。
一.產(chan)生(sheng)的原因
1) 一般進(jin)程(cheng)
正常情況下:子(zi)(zi)進(jin)程(cheng)(cheng)(cheng)由父進(jin)程(cheng)(cheng)(cheng)創建(jian),子(zi)(zi)進(jin)程(cheng)(cheng)(cheng)再創建(jian)新的(de)進(jin)程(cheng)(cheng)(cheng)。父子(zi)(zi)進(jin)程(cheng)(cheng)(cheng)是一個(ge)異步過(guo)程(cheng)(cheng)(cheng),父進(jin)程(cheng)(cheng)(cheng)永(yong)遠(yuan)無法預測子(zi)(zi)進(jin)程(cheng)(cheng)(cheng)的(de)結(jie)束,所(suo)以(yi),當子(zi)(zi)進(jin)程(cheng)(cheng)(cheng)結(jie)束后(hou),它的(de)父進(jin)程(cheng)(cheng)(cheng)會調(diao)用wait()或waitpid()取得子(zi)(zi)進(jin)程(cheng)(cheng)(cheng)的(de)終止狀態,回收掉(diao)子(zi)(zi)進(jin)程(cheng)(cheng)(cheng)的(de)資源(yuan)。
2)孤兒進程
孤兒進(jin)程(cheng):父進(jin)程(cheng)結束了(le),而它的(de)一個(ge)或(huo)多(duo)個(ge)子進(jin)程(cheng)還在(zai)運行,那么(me)這(zhe)些子進(jin)程(cheng)就成為孤兒進(jin)程(cheng)(father died)。子進(jin)程(cheng)的(de)資源由init進(jin)程(cheng)(進(jin)程(cheng)號PID = 1)回(hui)收。
3)僵尸進程
僵尸(shi)進(jin)(jin)(jin)(jin)程(cheng):子(zi)(zi)進(jin)(jin)(jin)(jin)程(cheng)退出了,但是父(fu)進(jin)(jin)(jin)(jin)程(cheng)沒有用(yong)wait或waitpid去獲取子(zi)(zi)進(jin)(jin)(jin)(jin)程(cheng)的狀態信息,那么子(zi)(zi)進(jin)(jin)(jin)(jin)程(cheng)的進(jin)(jin)(jin)(jin)程(cheng)描述(shu)符仍然保存(cun)在系統中,這種進(jin)(jin)(jin)(jin)程(cheng)稱為僵死(si)進(jin)(jin)(jin)(jin)程(cheng)。
二.問題危害
注意:unix提供了一(yi)種機制保(bao)證父進程知道(dao)子(zi)進程結束時的狀態(tai)信(xin)息。
這種機(ji)制是:在每個進程退出的時(shi)候,內(nei)核會釋放(fang)所(suo)有的資源,包(bao)括(kuo)打開的文件,占用(yong)的內(nei)存(cun)等。但是仍(reng)保留一部(bu)分信息(進程號(hao)PID,退出狀態,運行時(shi)間等)。直到父進程通過wait或(huo)waitpid來取時(shi)才釋放(fang)。
但是(shi)這(zhe)樣就會產(chan)生問題:如(ru)果(guo)(guo)父(fu)進(jin)(jin)程(cheng)(cheng)不調用(yong)(yong)wait或waitpid的(de)(de)話,那(nei)么保留(liu)的(de)(de)信息就不會被釋放,其(qi)進(jin)(jin)程(cheng)(cheng)號(hao)(hao)就會被一直占用(yong)(yong),但是(shi)系統所能(neng)使用(yong)(yong)的(de)(de)進(jin)(jin)程(cheng)(cheng)號(hao)(hao)是(shi)有(you)限的(de)(de),如(ru)果(guo)(guo)大量產(chan)生僵死進(jin)(jin)程(cheng)(cheng),將因(yin)沒有(you)可用(yong)(yong)的(de)(de)進(jin)(jin)程(cheng)(cheng)號(hao)(hao)而導致系統無法產(chan)生新的(de)(de)進(jin)(jin)程(cheng)(cheng),這(zhe)就是(shi)僵尸進(jin)(jin)程(cheng)(cheng)的(de)(de)危害
孤兒(er)進(jin)程(cheng)是沒(mei)(mei)有(you)父進(jin)程(cheng)的進(jin)程(cheng),它由(you)init進(jin)程(cheng)循環的wait()回(hui)收資源,init進(jin)程(cheng)充當父進(jin)程(cheng)。因此孤兒(er)進(jin)程(cheng)并沒(mei)(mei)有(you)什(shen)么危害。
補充:任(ren)何一(yi)個子進(jin)(jin)程(cheng)(cheng)(init除外)在(zai)exit()之(zhi)后,并非馬上就消(xiao)失掉,而是(shi)(shi)留下一(yi)個稱為僵尸進(jin)(jin)程(cheng)(cheng)的數據結構,等待(dai)父(fu)進(jin)(jin)程(cheng)(cheng)去處理。如(ru)果父(fu)進(jin)(jin)程(cheng)(cheng)在(zai)子進(jin)(jin)程(cheng)(cheng)exit()之(zhi)后,沒(mei)有(you)及時(shi)處理,出現僵尸進(jin)(jin)程(cheng)(cheng),并可以用ps命令去查看,它的狀態是(shi)(shi)“Z”。
三(san).解決方案
1)kill殺死元(yuan)兇父進程(cheng)(一般不(bu)用)
嚴格的說,僵(jiang)(jiang)尸(shi)進(jin)(jin)程(cheng)(cheng)(cheng)并不是問(wen)題的根(gen)源(yuan),罪魁禍(huo)首是產生(sheng)大量僵(jiang)(jiang)死進(jin)(jin)程(cheng)(cheng)(cheng)的父(fu)進(jin)(jin)程(cheng)(cheng)(cheng)。因此,我(wo)們可以直接除掉元(yuan)兇,通(tong)過kill發送SIGTERM或者SIGKILL信號。元(yuan)兇死后,僵(jiang)(jiang)尸(shi)進(jin)(jin)程(cheng)(cheng)(cheng)進(jin)(jin)程(cheng)(cheng)(cheng)變成(cheng)孤兒進(jin)(jin)程(cheng)(cheng)(cheng),由init充當父(fu)進(jin)(jin)程(cheng)(cheng)(cheng),并回收資源(yuan)。
或者運行:kill -9 父進程的(de)pid值、
2)父進程(cheng)用wait或(huo)waitpid去回收(shou)資源(方案不好)
父進(jin)(jin)程(cheng)(cheng)(cheng)通過wait或waitpid等(deng)函數去(qu)等(deng)待子進(jin)(jin)程(cheng)(cheng)(cheng)結束,但是不好,會導(dao)致父進(jin)(jin)程(cheng)(cheng)(cheng)一(yi)直等(deng)待被(bei)掛起,相當(dang)于一(yi)個進(jin)(jin)程(cheng)(cheng)(cheng)在干活,沒(mei)有(you)起到(dao)多進(jin)(jin)程(cheng)(cheng)(cheng)的作用。
3)通過信(xin)號機(ji)制(zhi),在(zai)處理函(han)數中調(diao)用wait,回收資(zi)源
通過信(xin)號(hao)機制(zhi),子(zi)進(jin)程(cheng)退出(chu)時向(xiang)父進(jin)程(cheng)發送SIGCHLD信(xin)號(hao),父進(jin)程(cheng)調用(yong)signal(SIGCHLD,sig_child)去(qu)處理SIGCHLD信(xin)號(hao),在信(xin)號(hao)處理函數sig_child()中調用(yong)wait進(jin)行處理僵尸進(jin)程(cheng)。什(shen)么時候(hou)得到子(zi)進(jin)程(cheng)信(xin)號(hao),什(shen)么時候(hou)進(jin)行信(xin)號(hao)處理,父進(jin)程(cheng)可以(yi)繼續(xu)干其他活(huo),不用(yong)去(qu)阻塞(sai)等待(dai)。
例子1:
 #include
  #include
  #include
  #include
  #include
static void sig_child(int signo);
int main()
{
pid_t pid;
//創(chuang)建捕捉子進程退出信(xin)號
signal(SIGCHLD,sig_child);
pid = fork();
if (pid < 0)
{
perror("fork error:");
exit(1);
}
else if (pid == 0)
{
printf("I am child process,pid id %d.I am exiting.\n",getpid());
exit(0);
}
printf("I am father process.I will sleep two seconds\n");
//等待(dai)子進程先(xian)退(tui)出(chu)
sleep(2);
//輸(shu)出進(jin)程(cheng)信息
system("ps -o pid,ppid,state,tty,command");
printf("father process is exiting.\n");
return 0;
}
static void sig_child(int signo)
{
pid_t pid;
int stat;
//處理僵尸進程
while ((pid = waitpid(-1, &stat, WNOHANG)) >0)
printf("child %d terminated.\n", pid);
}
4)fork兩(liang)次(ci)
fork兩次,父(fu)進程(cheng)(cheng)(cheng)fork一個子(zi)進程(cheng)(cheng)(cheng),子(zi)進程(cheng)(cheng)(cheng)在fork出一個孫子(zi)進程(cheng)(cheng)(cheng),然后子(zi)進程(cheng)(cheng)(cheng)立馬退出,并(bing)由父(fu)進程(cheng)(cheng)(cheng)去wait回(hui)(hui)收,這(zhe)個過程(cheng)(cheng)(cheng)不需要等(deng)待,然后父(fu)進程(cheng)(cheng)(cheng)可以去干(gan)其他的活。孫子(zi)進程(cheng)(cheng)(cheng)因為(wei)子(zi)進程(cheng)(cheng)(cheng)退出會成(cheng)為(wei)孤兒進程(cheng)(cheng)(cheng),那它可以由init充當父(fu)進程(cheng)(cheng)(cheng),并(bing)回(hui)(hui)收。這(zhe)樣父(fu)進程(cheng)(cheng)(cheng)和孫子(zi)進程(cheng)(cheng)(cheng)就(jiu)可以同時(shi)干(gan)活,互不影(ying)響,就(jiu)實(shi)現了(le)多(duo)進程(cheng)(cheng)(cheng)。
例子2:
 #include
  #include
  #include
  #include
int main()
{
pid_t pid;
//創建第一個(ge)子進程
pid = fork();
if (pid < 0)
{
perror("fork error:");
exit(1);
}
//第一個子進程
else if (pid == 0)
{
//子(zi)(zi)進程再(zai)創(chuang)建(jian)子(zi)(zi)進程
printf("I am the first child process.pid:%d\tppid:%d\n",getpid(),getppid());
pid = fork();
if (pid < 0)
{
perror("fork error:");
exit(1);
}
//第一(yi)個子進程退(tui)出
else if (pid >0)
{
printf("first procee is exited.\n");
exit(0);
}
//第二個子進程
//睡眠3s保證第一(yi)個子(zi)(zi)進(jin)(jin)程退出,這樣第二個子(zi)(zi)進(jin)(jin)程的父親(qin)就是init進(jin)(jin)程里
sleep(3);
printf("I am the second child process.pid: %d\tppid:%d\n",getpid(),getppid());
exit(0);
}
//父進(jin)(jin)程處理(li)第一個子進(jin)(jin)程退出
if (waitpid(pid, NULL, 0) != pid)
{
perror("waitepid error:");
exit(1);
}
exit(0);
return 0;
}
四.補充測試(shi)程(cheng)序
1)孤兒進程(cheng)測試程(cheng)序
 #include
  #include
  #include
  #include
int main()
{
pid_t pid;
//創建一個進程(cheng)
pid = fork();
//創建失敗
if (pid < 0)
{
perror("fork error:");
exit(1);
}
//子進程
if (pid == 0)
{
printf("I am the child process.\n");
//輸出進(jin)程(cheng)ID和父進(jin)程(cheng)ID
printf("pid: %d\tppid:%d\n",getpid(),getppid());
printf("I will sleep five seconds.\n");
//睡(shui)眠(mian)5s,保證父進程先退出
sleep(5);
printf("pid: %d\tppid:%d\n",getpid(),getppid());
printf("child process is exited.\n");
}
//父進程
else
{
printf("I am father process.\n");
//父進程睡眠1s,保證子進程輸(shu)出進程id
sleep(1);
printf("father process is exited.\n");
}
return 0;
}
2)僵(jiang)尸(shi)進程測(ce)試程序1
int main()
{
pid_t pid;
pid = fork();
if (pid < 0)
{
perror("fork error:");
exit(1);
}
else if (pid == 0)
{
printf("I am child process.I am exiting.\n");
exit(0);
}
printf("I am father process.I will sleep two seconds\n");
//等待子進程先退(tui)出
sleep(2);
//輸出進程(cheng)信(xin)息
system("ps -o pid,ppid,state,command");
printf("father process is exiting.\n");
return 0;
}
3)僵(jiang)尸進(jin)程測試程序2
  #include
  #include
  #include
  #include
int main()
{
pid_t pid;
//循環(huan)創建子進(jin)程
while(1)
{
pid = fork();
if (pid < 0)
{
perror("fork error:");
exit(1);
}
else if (pid == 0)
{
printf("I am a child process.\nI am exiting.\n");
//子進(jin)程退出,成為僵尸進(jin)程
exit(0);
}
else
{
//父進程休眠20s繼續創建子進程
sleep(20);
continue;
}
}
return 0;
}
4)僵(jiang)尸進程(cheng)測(ce)試程(cheng)序2--測(ce)試效果
運行(xing)可執行(xing)程序顯示(shi):
I am a child process.
I am exiting.
I am a child process.
I am exiting.
I am a child process.
I am exiting.
I am a child process.
I am exiting.
I am a child process.
I am exiting.
I am a child process.
I am exiting.
Killed
開另外一個終端:
運行:
ps -a -o pid,ppid,state,cmd
顯(xian)示:(狀(zhuang)態Z代表僵(jiang)尸進程(cheng))
S PID PPID CMD
S 3213 2529 ./pid1
 Z 3214 3213 [pid1]
  Z 3215 3213 [pid1]
  Z 3219 3213 [pid1]
  Z 3220 3213 [pid1]
  Z 3221 3213 [pid1]
R 3223 3104 ps -a -o state,pid,ppid,cmd
用第一種(zhong)方法,解(jie)決(jue)僵尸進(jin)程,殺死其父(fu)進(jin)程
運行(xing):kill -9 3213
注意:僵尸進程(cheng)(cheng)無法用kill直接(jie)殺死(si),如kill -9 3214,再用上(shang)面命令去查(cha)看進程(cheng)(cheng)狀態,發現3214進程(cheng)(cheng)還(huan)在。
五. 參考文獻
//www.cnblogs.com/Anker/p/3271773.html
《unix環境高級編程(cheng)》第八章