必威电竞|足球世界杯竞猜平台

僵尸進程
來源:互聯網

僵尸進程是在類UNIX操作系統中,指已完成執行(通過exit系統調用,或運行時發生致命錯誤或收到終止信號所致),但在操作系統的進程表中仍然存在其進程控制塊,處于"終止狀態"的進程。這發生于子進程需要保留表項以允許其父進程讀取子進程的退出狀態:一旦退出態通過wait系統調用讀取,僵尸進程條目就從進程表中刪除,稱之為"回收"(reaped)。正常情況下,進程直接被其父進程wait并由系統回收。進程長時間保持僵尸狀態一般是錯誤的并導致資源泄漏。

形成原因

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

特征

unix系統中,一個進程結束了,但是他的父進程沒有等待(調用wait / waitpid)他,那么他將變成一個僵尸進程。但是如果該進程的父進程已經先結束了,那么該進程就不會變成僵尸進程,因為每個進程結束的時候,系統都會掃描當前系統中所運行的所有進程,看有沒有哪個進程是剛剛結束的這個進程的子進程,如果是的話,就由Init 來接管他,成為他的父進程。

危害

由于子進程的結束和父進程的運行是一個異步過程,即父進程永遠無法預測子進程到底什么時候結束。不會。因為UNⅨ提供了一種機制可以保證只要父進程想知道子進程結束時的狀態信息,就可以得到。這種機制就是:在每個進程退出的時候,內核釋放該進程所有的資源,包括打開的文件,占用的內存等。但是仍然為其保留一定的信息(包括進程號the process ID,退出狀態the termination status of the process,運行時間the amount of CPU 時間 taken by the process等)。直到父進程通過wait / waitpid來取時才釋放。但這樣就導致了問題,如果進程不調用wait / waitpid的話,那么保留的那段信息就不會釋放,其進程號就會一直被占用,但是系統所能使用的進程號是有限的,如果大量的產生僵死進程,將因為沒有可用的進程號而導致系統不能產生新的進程。此即為僵尸進程的危害,應當避免。

避免方法

方法一

父進程通過wait和waitpid等函數等待子進程結束,這會導致父進程掛起。

方法二

如果父進程很忙,那么可以用signal函數為SIGCHLD安裝handler,因為子進程結束后,父進程會收到該信號,可以在handler中調用wait回收。

方法三

如果父進程不關心子進程什么時候結束,那么可以用signal(SIGCHLD,SIG_IGN)通知內核,自己對子進程的結束不感興趣,那么子進程結束后,內核會回收,并不再給父進程發送信號。

方法四

還有些技巧,就是fork兩次,父進程fork一個子進程,然后繼續工作,子進程fork一個孫進程后退出,那么孫進程被init接管,孫進程結束后,init會回收。不過子進程的回收還要自己做。

查看方法

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

假設

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

補救辦法

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

產生過程

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

進程處理

它需要它的父進程來為它收尸,如果它的父進程沒安裝SIGCHLD信號處理函數調用wait或waitpid()等待子進程結束,又沒有顯式忽略該信號,那么它就一直保持僵尸狀態;存在的問題:如果父進程是一個循環,不會結束,那么子進程就會一直保持僵尸狀態,這就是為什么系統中有時會有很多的僵尸進程,系統的性能可能會受到影響。如果這時父進程結束了,那么init進程會自動接手這個子進程,為它收尸,它還是能被清除的。4、子進程結束后為什么要進入僵尸狀態?因為父進程可能要取得子進程的退出狀態等信息。5、僵尸狀態是每個子進程必經的狀態嗎?是的。任何一個子進程(init除外)在exit()之后,并非馬上就消失掉,而是留下一個稱為僵尸進程(Zombie)的數據結構,等待父進程處理。這是每個子進程在結束時都要經過的階段。如果子進程在exit()之后,父進程沒有來得及處理,這時用ps命令就能看到子進程的狀態是“Z”。如果父進程能及時處理,可能用ps命令就來不及看到子進程的僵尸狀態,但這并不等于子進程不經過僵尸狀態。* 如果父進程在子進程結束之前退出,則子進程將由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\n#include "apue.h"\n#include \nint main(void) ...{\npid_t pid;\nif ((pid = fork()) < 0)\n{\nerr_sys("fork error");\n} else if (pid == 0) { /* first child */\nif ((pid = fork()) < 0)\nerr_sys("fork error");\nelse if (pid > 0)\nexit(0); /* parent from second fork == first child */\n/ * 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.*/\nsleep⑵;\nprintf("second child,parent pid = %d ",getppid());\nexit(0);\n}\nif (waitpid(pid,NULL,0) != pid) /* wait for first child */\nerr_sys("waitpid error");\n/ * We're the parent (the original process); we continue executing,knowing that we're not the parent of the second child.*/\nexit(0);\n}

小結

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

參考資料 >

Linux系統僵尸進程詳解.博客園.2024-09-18

Linux系統下的僵尸進程(概念、產生、危害、避免).CSDN博客.2024-09-18

僵尸進程:Linux系統中的隱秘威脅|如何查看并清理僵尸進程.人言兌.2024-09-18

生活家百科家居網