殭屍進程

殭屍進程

殭屍進程是當子進程比父進程先結束,而父進程又沒有回收子進程,釋放子進程占用的資源,此時子進程將成為一個殭屍進程。如果父進程先退出 ,子進程被init接管,子進程退出後init會回收其占用的相關資源

我們都知道進程的工作原理。我們啟動一個程式,開始我們的任務,然後等任務結束了,我們就停止這個進程。 進程停止後, 該進程就會從進程表中移除。

你可以通過 System-Monitor 查看當前進程。

In UNIX System terminology, a process that has terminated,but whose parent has not yet waited for it, is called a zombie. 在UNIX 系統中,一個進程結束了,但是他的父進程沒有等待(調用wait / waitpid)他, 那么他將變成一個殭屍進程。 但是如果該進程的父進程已經先結束了,那么該進程就不會變成殭屍進程, 因為每個進程結束的時候,系統都會掃描當前系統中所運行的所有進程, 看有沒有哪個進程是剛剛結束的這個進程的子進程,如果是的話,就由Init 來接管他,成為他的父進程……

基本介紹

  • 中文名:殭屍進程
  • 外文名:Zombie process
  • 危害:導致系統不能產生新的進程
  • 所屬學科:計算機
  • 解決方式:可用kill-SIGKILL父進程ID來解決
基本概念,危害,避免示例,進程處理,假設,補救辦法,產生過程,查看方法,避免方法,進程清除,小結,

基本概念

一個進程在調用exit命令結束自己的生命的時候,其實它並沒有真正的被銷毀, 而是留下一個稱為殭屍進程(Zombie)的數據結構(系統調用exit,它的作用是 使進程退出,但也僅僅限於將一個正常的進程變成一個殭屍進程,並不能將其完全銷毀)
殭屍進程殭屍進程

危害

由於子進程的結束和父進程的運行是一個異步過程,即父進程永遠無法預測子進程 到底什麼時候結束. 那么會不會因為父進程太忙來不及wait子進程,或者說不知道 子進程什麼時候結束,而丟失子進程結束時的狀態信息呢? 不會。因為UNⅨ提供了一種機制可以保證只要父進程想知道子進程結束時的狀態信息, 就可以得到。這種機制就是: 在每個進程退出的時候,核心釋放該進程所有的資源,包括打開的檔案,占用的記憶體等。但是仍然為其保留一定的信息(包括進程號the process ID,退出狀態the termination status of the process,運行時間the amount of CPU time taken by the process等)。直到父進程通過wait / waitpid來取時才釋放. 但這樣就導致了問題,如果進程不調用wait / waitpid的話,那么保留的那段信息就不會釋放,其進程號就會一直被占用,但是系統所能使用的進程號是有限的,如果大量的產生殭屍進程,將因為沒有可用的進程號而導致系統不能產生新的進程. 此即為殭屍進程的危害,應當避免。
殭屍進程的避免
⒈父進程通過wait和waitpid等函式等待子進程結束,這會導致父進程掛起。
⒉ 如果父進程很忙,那么可以用signal函式為SIGCHLD安裝handler,因為子進程結束後, 父進程會收到該信號,可以在handler中調用wait回收。
⒊ 如果父進程不關心子進程什麼時候結束,那么可以用signal(SIGCHLD,SIG_IGN) 通知核心,自己對子進程的結束不感興趣,那么子進程結束後,核心會回收, 並不再給父進程傳送信號。
⒋ 還有一些技巧,就是fork兩次,父進程fork一個子進程,然後繼續工作,子進程fork一 個孫進程後退出,那么孫進程被init接管,孫進程結束後,init會回收。不過子進程的回收 還要自己做。

避免示例

Example Recall our discussion in Section 8.5 about zombie processes. If we want to write a process so that it forks a child but we don't want to wait for the child to complete and we don't want the child to become a zombie until we terminate,the trick is to call fork twice. The program in Figure 8.8 does this. We call sleep in the second child to ensure that the first child terminates before printing the parent process ID. After a fork,either the parent or the child can continue executing; we never know which will resume execution first. If we didn't put the second child to sleep,and if it resumed execution after the fork before its parent,the parent process ID that it printed would be that of its parent,not process ID 1. Executing the program in Figure 8.8 gives us $ ./a.out $ second child,parent pid = 1 Note that the shell prints its prompt when the original process terminates,which is before the second child prints its parent process ID. Figure 8.8. Avoid zombie processes by calling fork twice
#include "apue.h"
#include <sys/wait.h>
int main(void) ...{
pid_t pid;
if ((pid = fork()) < 0)
{
err_sys("fork error");
} else if (pid == 0) { /* first child */
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid > 0)
exit(0); /* parent from second fork == first child */
/ * We're the second child; our parent becomes init as soonas our real parent calls exit() in the statement above. Here's where we'd continue executing,knowing that whenwe're done,init will reap our status.*/
sleep⑵;
printf("second child,parent pid = %d ",getppid());
exit(0);
}
if (waitpid(pid,NULL,0) != pid) /* wait for first child */
err_sys("waitpid error");
/ * We're the parent (the original process); we continue executing,knowing that we're not the parent of the second child.*/
exit(0);
}

進程處理

它需要它的父進程來為它收屍,如果它的父進程沒安裝SIGCHLD信號處理函式調用wait或waitpid()等待子進程結束,又沒有顯式忽略該信號,那么它就一直保持殭屍狀態; 存在的問題:如果父進程是一個循環,不會結束,那么子進程就會一直保持殭屍狀態,這就是為什麼系統中有時會有很多的殭屍進程,系統的性能可能會受到影響。如果這時父進程結束了,那么init進程會自動接手這個子進程,為它收屍,它還是能被清除的。4、子進程結束後為什麼要進入殭屍狀態? 因為父進程可能要取得子進程的退出狀態等信息。5、殭屍狀態是每個子進程必經的狀態嗎? 是的。 任何一個子進程(init除外)在exit()之後,並非馬上就消失掉,而是留下一個稱為殭屍進程(Zombie)的數據結構,等待父進程處理。這是每個子進程在結束時都要經過的階段。如果子進程在exit()之後,父進程沒有來得及處理,這時用ps命令就能看到子進程的狀態是“Z”。如果父進程能及時 處理,可能用ps命令就來不及看到子進程的殭屍狀態,但這並不等於子進程不經過殭屍狀態。 * 如果父進程在子進程結束之前退出,則子進程將由init接管。init將會以父進程的身份對殭屍狀態的子進程進行處理。6、如何查看殭屍進程: $ ps -el 其中,有標記為Z的進程就是殭屍進程 S代表休眠狀態;D代表不可中斷的休眠狀態;R代表運行狀態;Z代表僵死狀態;T代表停止或跟蹤狀態

假設

在fork()/execve()過程中,假設子進程結束時父進程仍存在,而父進程fork()之前既沒安裝SIGCHLD信號處理函式調用waitpid()等待子進程結束,又沒有顯式忽略該信號,則子進程成為殭屍進程,無法正常結束,此時即使是root身份kill -9也不能殺死殭屍進程。

補救辦法

殺死殭屍進程的父進程(殭屍進程的父進程必然存在),殭屍進程成為"孤兒進程",過繼給1號進程init,init始終會負責清理殭屍進程。

產生過程

一個進程在調用exit命令結束自己的生命的時候,其實它並沒有真正的被銷毀,而是留下一個稱為殭屍進程(Zombie)的數據結構(系統調用exit,它的作用是使進程退出,但也僅僅限於將一個正常的進程變成一個殭屍進程,並不能將其完全銷毀)。在Linux進程的狀態中,殭屍進程是非常特殊的一種,它已經放棄了幾乎所有記憶體空間,沒有任何可執行代碼,也不能被調度,僅僅在進程列表中保留一個位置,記載該進程的退出狀態等信息供其他進程收集,除此之外,殭屍進程不再占有任何記憶體空間。它需要它的父進程來為它收屍,如果他的父進程沒安裝SIGCHLD信號處理函式調用wait或waitpid()等待子進程結束,又沒有顯式忽略該信號,那么它就一直保持殭屍狀態,如果這時父進程結束了,那么init進程自動會接手這個子進程,為它收屍,它還是能被清除的。但是如果父進程是一個循環,不會結束,那么子進程就會一直保持殭屍狀態,這就是為什麼系統中有時會有很多的殭屍進程。
linux中的殭屍進程linux中的殭屍進程

查看方法

查看殭屍進程,利用命令ps,可以看到有標記為Z的進程就是殭屍進程。

避免方法

具體步驟
1)在SVR4中,如果調用signal或sigset將SIGCHLD的配置設定為忽略,則不會產生僵死子進程。另外,使用SVR4版的sigaction,則可設定SA_NOCLDWAIT標誌以避免子進程僵死。
Linux中也可使用這個,在一個程式的開始調用這個函式
signal(SIGCHLD,SIG_IGN);
2)調用fork兩次。程式8 - 5 實現了這一點。
3)用waitpid等待子進程返回.
===========================================
zombie進程是僵死進程。防止它的辦法,一是用wait,waitpid之類的函式獲得
進程的終止狀態,以釋放資源。另一個是fork兩次
===========================================
defunct進程只是在process table里還有一個記錄,其他的資源沒有占用,除非你的系統的process個數的限制已經快超過了,zombie進程不會有更多的壞處。
可能的方法就是reboot系統可以消除zombie進程。
===========================================
任何程式都有殭屍狀態,它占用一點記憶體資源(也就是進程表里還有一個記錄),僅僅是表象而已不必害怕。如果程式有問題有機會遇見,解決大批量殭屍簡單有效的辦法是重起。kill是無任何效果的
"起死回生"
在Unix下的一些進程的運作方式。當一個進程死亡時,它並不是完全的消失了。進程終止,它不再運行,但是還有一些殘留的小東西等待父進程收回。這些殘留的東西包括子進程的返回值和其他的一些東西。當父進程fork() 一個子進程後,它必須用 wait() 或者 waitpid() 等待子進程退出。正是這個 wait() 動作來讓子進程的殘留物消失。
自然的,在上述規則之外有個例外:父進程可以忽略 SIGCLD軟中斷而不必要 wait()。可以這樣做到(在支持它的系統上,比如Linux):
main()
{
signal(SIGCLD,SIG_IGN); /* now I don't have to wait()! */
.
.
fork();
fork();
fork(); /* Rabbits,rabbits,rabbits! */
}
當前子進程死亡時父進程沒有 wait(),通常用 ps 可以看到它被顯示為“”。它將永遠保持這樣 直到父進程wait(),或者按以下方法處理。
這裡是你必須知道的另一個規則:當父進程在它wait()子進程之前死亡了(假定它沒有忽略 SIGCLD),子進程將把 init(pid 1)進程作為它的父進程。如果子進程工作得很好並能夠控制,這並不是問題。但如果子進程已經是 defunct,我們就有了一點小麻煩。看,原先的父進程不可能再 wait(),因為它已經消亡了。這樣,init 怎么知道 wait() 這些 zombie 進程。
答案:不可預料的。在一些系統上,init周期性的破壞掉它所有的defunct進程。在另外一些系統中,它乾脆拒絕成為任何defunct進程的父進程,而是馬上毀滅它們。如果你使用上述系統的一種,可以寫一個簡單的循環,用屬於init的defunct進程填滿進程表。這大概不會令你的系統管理員很高興吧?
你的任務:確定你的父進程不要忽略 SIGCLD,也不要 wait() 它 fork() 的所有進程。不過,你也未必 要 總是這樣做(比如,你要起一個 daemon 或是別的什麼東西),但是你必須小心編程,如果你是一個 fork() 的新手。另外,也不要在心理上有任何束縛。

進程清除

改寫父進程
具體做法是接管SIGCHLD信號。子進程死後,會傳送SIGCHLD信號給父進程,父進程收到此信號後,執行waitpid()函式為子進程收屍。就是基於這樣的原理:就算父進程沒有調用wait,核心也會向它傳送SIGCHLD訊息,而此時,儘管對它的默認處理是忽略,如果想回響這個訊息,可以設定一個處理函式。
把父進程殺掉
父進程死後,殭屍進程成為"孤兒進程",過繼給1號進程init,init始終會負責清理殭屍進程.它產生的所有殭屍進程也跟著消失。
===========================================
在Linux中可以用  ps auwx  發現殭屍進程
a all w/ tty,including other users 所有視窗和終端,包括其他用戶的進程
u user-oriented 面向用戶(用戶友好)
-w,w wide output 寬格式輸出
x processes w/o controlling ttys  在殭屍進程後面 會標註
ps axf  看進程樹,以樹形方式現實進程列表
ps axm  會把執行緒列出來,在linux下進程和執行緒是統一的,是輕量級進程的兩種方式。
ps axu  顯示進程的詳細狀態
===========================================
killall  kill -15  kill -9  一般都不能殺掉 defunct進程
用了kill -15,kill -9以後 之後反而會多出更多的殭屍進程
kill -kill pid  fuser -k pid  可以考慮殺死他的parent process,
kill -9 他的parent process
===========================================
一個已經終止,但是其父進程尚未對其進行善後處理(獲取終止子進程的有關信息、釋放它仍占用的資源)的進程被稱為僵死進程(Zombie Process)。

小結

子進程成為 defunct 直到父進程wait(),除非父進程忽略了 SIGCLD。
更進一步,父進程沒有 wait() 就消亡(仍假設父進程沒有忽略 SIGCLD)的子進程(活動的或者 defunct)成為 init 的子進程,init 用重手法處理它們。

相關詞條

熱門詞條

聯絡我們