前段時間,由于研究經典面試題,把孤兒進程和僵尸進程也總結了一下。
我們有這樣一個問題:孤兒進程和僵尸進程,怎么產生的?有什么危害?怎么去預防?
下面是針對此問題的總結與概括。
一.產生的原因
1) 一般進程
正常情況下:子進程由父進程創建,子進程再創建新的進程。父子進程是一個異步過程,父進程永遠無法預測子進程的結束,所以,當子進程結束后,它的父進程會調用wait()或waitpid()取得子進程的終止狀態,回收掉子進程的資源。
2)孤兒進程
孤兒進程:父進程結束了,而它的一個或多個子進程還在運行,那么這些子進程就成為孤兒進程(father died)。子進程的資源由init進程(進程號PID = 1)回收。
3)僵尸進程
僵尸進程:子進程退出了,但是父進程沒有用wait或waitpid去獲取子進程的狀態信息,那么子進程的進程描述符仍然保存在系統中,這種進程稱為僵死進程。
二.問題危害
注意:unix提供了一種機制保證父進程知道子進程結束時的狀態信息。
這種機制是:在每個進程退出的時候,內核會釋放所有的資源,包括打開的文件,占用的內存等。但是仍保留一部分信息(進程號PID,退出狀態,運行時間等)。直到父進程通過wait或waitpid來取時才釋放。
但是這樣就會產生問題:如果父進程不調用wait或waitpid的話,那么保留的信息就不會被釋放,其進程號就會被一直占用,但是系統所能使用的進程號是有限的,如果大量產生僵死進程,將因沒有可用的進程號而導致系統無法產生新的進程,這就是僵尸進程的危害
孤兒進程是沒有父進程的進程,它由init進程循環的wait()回收資源,init進程充當父進程。因此孤兒進程并沒有什么危害。
補充:任何一個子進程(init除外)在exit()之后,并非馬上就消失掉,而是留下一個稱為僵尸進程的數據結構,等待父進程去處理。如果父進程在子進程exit()之后,沒有及時處理,出現僵尸進程,并可以用ps命令去查看,它的狀態是“Z”。
三.解決方案
1)kill殺死元兇父進程(一般不用)
嚴格的說,僵尸進程并不是問題的根源,罪魁禍首是產生大量僵死進程的父進程。因此,我們可以直接除掉元兇,通過kill發送SIGTERM或者SIGKILL信號。元兇死后,僵尸進程進程變成孤兒進程,由init充當父進程,并回收資源。
或者運行:kill -9 父進程的pid值、
2)父進程用wait或waitpid去回收資源(方案不好)
父進程通過wait或waitpid等函數去等待子進程結束,但是不好,會導致父進程一直等待被掛起,相當于一個進程在干活,沒有起到多進程的作用。
3)通過信號機制,在處理函數中調用wait,回收資源
通過信號機制,子進程退出時向父進程發送SIGCHLD信號,父進程調用signal(SIGCHLD,sig_child)去處理SIGCHLD信號,在信號處理函數sig_child()中調用wait進行處理僵尸進程。什么時候得到子進程信號,什么時候進行信號處理,父進程可以繼續干其他活,不用去阻塞等待。
例子1:
#include
#include
#include
#include
#include
static void sig_child(int signo);
int main()
{
pid_t pid;
//創建捕捉子進程退出信號
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");
//等待子進程先退出
sleep(2);
//輸出進程信息
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兩次
fork兩次,父進程fork一個子進程,子進程在fork出一個孫子進程,然后子進程立馬退出,并由父進程去wait回收,這個過程不需要等待,然后父進程可以去干其他的活。孫子進程因為子進程退出會成為孤兒進程,那它可以由init充當父進程,并回收。這樣父進程和孫子進程就可以同時干活,互不影響,就實現了多進程。
例子2:
#include
#include
#include
#include
int main()
{
pid_t pid;
//創建第一個子進程
pid = fork();
if (pid < 0)
{
perror("fork error:");
exit(1);
}
//第一個子進程
else if (pid == 0)
{
//子進程再創建子進程
printf("I am the first child process.pid:%d\tppid:%d\n",getpid(),getppid());
pid = fork();
if (pid < 0)
{
perror("fork error:");
exit(1);
}
//第一個子進程退出
else if (pid >0)
{
printf("first procee is exited.\n");
exit(0);
}
//第二個子進程
//睡眠3s保證第一個子進程退出,這樣第二個子進程的父親就是init進程里
sleep(3);
printf("I am the second child process.pid: %d\tppid:%d\n",getpid(),getppid());
exit(0);
}
//父進程處理第一個子進程退出
if (waitpid(pid, NULL, 0) != pid)
{
perror("waitepid error:");
exit(1);
}
exit(0);
return 0;
}
四.補充測試程序
1)孤兒進程測試程序
#include
#include
#include
#include
int main()
{
pid_t pid;
//創建一個進程
pid = fork();
//創建失敗
if (pid < 0)
{
perror("fork error:");
exit(1);
}
//子進程
if (pid == 0)
{
printf("I am the child process.\n");
//輸出進程ID和父進程ID
printf("pid: %d\tppid:%d\n",getpid(),getppid());
printf("I will sleep five seconds.\n");
//睡眠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,保證子進程輸出進程id
sleep(1);
printf("father process is exited.\n");
}
return 0;
}
2)僵尸進程測試程序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");
//等待子進程先退出
sleep(2);
//輸出進程信息
system("ps -o pid,ppid,state,command");
printf("father process is exiting.\n");
return 0;
}
3)僵尸進程測試程序2
#include
#include
#include
#include
int main()
{
pid_t pid;
//循環創建子進程
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");
//子進程退出,成為僵尸進程
exit(0);
}
else
{
//父進程休眠20s繼續創建子進程
sleep(20);
continue;
}
}
return 0;
}
4)僵尸進程測試程序2--測試效果
運行可執行程序顯示:
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
顯示:(狀態Z代表僵尸進程)
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
用第一種方法,解決僵尸進程,殺死其父進程
運行:kill -9 3213
注意:僵尸進程無法用kill直接殺死,如kill -9 3214,再用上面命令去查看進程狀態,發現3214進程還在。
五. 參考文獻
//www.cnblogs.com/Anker/p/3271773.html
《unix環境高級編程》第八章