Linux 僵尸進程產(chǎn)生原因及解決方法
Linux 允許進程查詢內(nèi)核以獲得其父進程的 PID,或者其任何子進程的執(zhí)行狀態(tài)。例如,進程可以創(chuàng)建一個子進程來執(zhí)行特定的任務(wù),然后調(diào)用諸如 wait() 這樣的一些庫函數(shù)檢查子進程是否終止。如果子進程已經(jīng)終止,那么,它的終止代號將告訴父進程這個任務(wù)是否已成功地完成。
為了遵循這些設(shè)計原則,不允許 Linux 內(nèi)核在進程一終止后就丟棄包含在進程描述符字段中的數(shù)據(jù)。只有父進程發(fā)出了與被終止的進程相關(guān)的 wait() 類系統(tǒng)調(diào)用之后,才允許這樣做。這就是引入僵死狀態(tài)的原因:盡管從技術(shù)上來說進程已死,但必須保存它的描述符,直到父進程得到通知。
如果一個進程已經(jīng)終止,但是它的父進程尚未調(diào)用 wait() 或 waitpid() 對它進行清理,這時的進程狀態(tài)稱為僵死狀態(tài),處于僵死狀態(tài)的進程稱為僵尸進程(zombie process)。任何進程在剛終止時都是僵尸進程,正常情況下,僵尸進程都立刻被父進程清理了。
僵尸進程是如何產(chǎn)生的
在UNIX 系統(tǒng)中,一個進程結(jié)束了,但是他的父進程沒有等待(調(diào)用wait / waitpid)他,那么他將變成一個僵尸進程。通過ps命令查看其帶有defunct的標志。僵尸進程是一個早已死亡的進程,但在進程表 (processs table)中仍占了一個位置(slot)。
但是如果該進程的父進程已經(jīng)先結(jié)束了,那么該進程就不會變成僵尸進程。因為每個進程結(jié)束的時候,系統(tǒng)都會掃描當前系統(tǒng)中所運行的所有進程,看看有沒有哪個 進程是剛剛結(jié)束的這個進程的子進程,如果是的話,就由Init進程來接管他,成為他的父進程,從而保證每個進程都會有一個父進程。而Init進程會自動 wait其子進程,因此被Init接管的所有進程都不會變成僵尸進程。
為了觀察到僵尸進程,我們自己寫一個不正常的程序,父進程 fork 出子進程,子進程終止,而父進程既不終止也不調(diào)用 wait 清理子進程:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int i = 100;
pid_t pid=fork();
if(pid < 0)
{
perror("fork failed.");
exit(1);
}
if(pid > 0)
{
printf("This is the parent process. My PID is %d.\n", getpid());
for(; i > 0; i--)
{
sleep(1);
}
}
else if(pid == 0)
{
printf("This is the child process. My PID is: %d. My PPID is: %d.\n", getpid(), getppid());
}
return 0;
}
把上面的代碼保存到文件 zomprocdemo.c 文件中,并執(zhí)行下面的命令編譯:
$ gcc zomprocdemo.c -o zomprocdemo
然后運行編譯出來的 zomprocdemo 程序:
$ ./zomprocdemo

此時子進程已經(jīng)退出,但是父進程沒有退出也沒有通過 wait() 調(diào)用處理子進程。我們使用 ps 命令查看進程的狀態(tài):

上圖紅框中的大寫字母 "Z" 說明 PID 為 112712 的進程此時處于僵死的狀態(tài)。
讓我們接著往下看!在結(jié)束 sleep 后父進程退出。當父進程退出后,子進程會變成孤兒進程,此時它會被一個管理進程收養(yǎng)。在不同的系統(tǒng)中,這個管理進程不太一樣,早期一般是 init 進程,Ubuntu 上是 upstart,還有近來的 Systemd。但是它們都完成相同的任務(wù),就是 wiat() 這些孤兒進程,并最終釋放它們占用的系統(tǒng)進程表中的資源。這樣,這些已經(jīng)僵死的孤兒進程就徹底的被清除了。
僵尸進程的危害
在進程退出的時候,內(nèi)核釋放該進程所有的資源,包括打開的文件,占用的內(nèi)存等。但是仍然為其保留一定的信息(包括進程號 PID,退出狀態(tài) the termination status of the process,運行時間 the amount of CPU time taken by the process 等)。直到父進程通過 wait / waitpid 來取時才釋放。
如果進程不調(diào)用 wait / waitpid 的話, 那么保留的那段信息就不會釋放,其進程號就會一直被占用,但是系統(tǒng)所能使用的進程號是有限的,如果大量的產(chǎn)生僵死進程,將因為沒有可用的進程號而導致系統(tǒng)不能產(chǎn)生新的進程。
如何處理僵尸進程
僵尸進程的產(chǎn)生是因為父進程沒有 wait() 子進程。所以如果我們自己寫程序的話一定要在父進程中通過 wait() 來避免僵尸進程的產(chǎn)生。
當系統(tǒng)中出現(xiàn)了僵尸進程時,我們是無法通過 kill 命令把它清除掉的。但是我們可以殺死它的父進程,讓它變成孤兒進程,并進一步被系統(tǒng)中管理孤兒進程的進程收養(yǎng)并清理。
下面的 demo 中,父進程通過 wait() 等待子進程結(jié)束:
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
pid_t pid;
pid = fork();
if (pid < 0)
{
perror("fork failed");
exit(1);
}
if (pid == 0) {
int i;
for (i = 3; i > 0; i--)
{
printf("This is the child\n");
sleep(1);
}
// exit with code 3 for test.
exit(3);
}
else
{
int stat_val;
wait(&stat_val);
if (WIFEXITED(stat_val))
{
printf("Child exited with code %d\n", WEXITSTATUS(stat_val));
}
}
return 0;
}
demo 中父進程不僅等待子進程結(jié)束,還通過 WEXITSTATUS 宏取到了子進程的 exit code。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
centos 7系統(tǒng)下安裝Jenkins的步驟詳解
Jenkins是一個開源軟件項目,是基于Java開發(fā)的一種持續(xù)集成工具,下面這篇文章主要給大家介紹了關(guān)于在centos 7系統(tǒng)下安裝Jenkins的步驟,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面來一起看看吧。2017-08-08
Linux下查詢外網(wǎng)IP的幾種方法總結(jié)
這篇文章主要介紹了Linux下查詢外網(wǎng)IP的幾種方法總結(jié)的相關(guān)資料,需要的朋友可以參考下2017-03-03
linux: centos設(shè)置ip以及連接外網(wǎng)的方法
本篇文章主要介紹了linux: centos設(shè)置ip以及連接外網(wǎng)的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-02-02
Ubuntu部署python3.5的開發(fā)和運行環(huán)境
這篇文章主要介紹了Ubuntu部署python3.5的開發(fā)和運行環(huán)境,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-01-01
基于Apache?Hudi在Google云構(gòu)建數(shù)據(jù)湖平臺的思路詳解
這篇文章主要介紹了基于Apache?Hudi在Google云構(gòu)建數(shù)據(jù)湖平臺,本文提供了有關(guān)如何使用上述工具構(gòu)建基本數(shù)據(jù)管道的基本介紹,需要的朋友可以參考下2022-04-04

