從Linux內核談殭屍進程

殭屍進程

摘自百度百科:

  • 殭屍進程是當子進程比父進程先結束,而父進程又沒有回收子進程,釋放子進程佔用的資源,此時子進程將成爲一個殭屍進程。如果父進程先退出 ,子進程被init接管,子進程退出後init會回收其佔用的相關資源。
  • 在UNIX 系統中,一個進程結束了,但是他的父進程沒有等待(調用wait / waitpid)他, 那麼他將變成一個殭屍進程。 但是如果該進程的父進程已經先結束了,那麼該進程就不會變成殭屍進程, 因爲每個進程結束的時候,系統都會掃描當前系統中所運行的所有進程, 看有沒有哪個進程是剛剛結束的這個進程的子進程,如果是的話,就由Init 來接管他,成爲他的父進程……
殭屍進程的危害

由於子進程的結束和父進程的運行是一個異步過程,即父進程永遠無法預測子進程 到底什麼時候結束. 那麼會不會因爲父進程太忙來不及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函數族等待子進程結束就會產生殭屍進程呢?Linux內核是如何實現的呢?

Linux內核對進程結束時的管理

  • 將task_struct中的標誌成員設置爲PE_EXITING
  • 調用del_timer_sync()刪除任一內核定時器。根據返回的結果,它確保沒有定時器在排隊,也沒有定時器處理程序在運行。
  • 如果BSD進程記賬功能是開啓的,do_exit()調用acct_update_integrals()來輸出記帳信息。
  • 然後調用exit_mm()函數釋放進程佔用的mm_struct,如果沒有別的進程使用它們(也就是說,這個地址空間沒有被共享),就徹底釋放它們。
  • 接下來調用sem_exit()函數。如果進程排隊等待IPC信號,它則離開隊列。
  • 調用exit_files()和exit_fs(),以分別遞減文件描述符、文件系統數據的引用計數。如果其中某個引用計數的數值降爲0,那麼就代表沒有進程在使用相應的資源,此時可以釋放。
  • 接着把存放在task_structg的exit_code成員中的任務退出代碼置爲由exit()提供的退出代碼,或者去完成任何其它有內核機規定的退出動作。退出代碼存放在這裏供父進程隨時檢索
  • 調用exit_notify()向父進程發送信號,給子進程重新找養父,養父爲線程組中的其它線程或者爲init進程,並把進程狀態(存放在task_struct結構的exit_state中)設成EXIT_ZOMBIE。
  • do_exit()調用schedule()切換到新的進程。因爲處於EXIT_ZOMBIE狀態的進程不會再被調度,所以這是進程所執行的最後一段代碼。do_exit()永不返回。
  • 至此,與進程相關聯的所有資源都被釋放掉了(假設該進程是這些資源的唯一使用者)。進程不可運行(實際上也沒有地址空間讓它運行)並處於EXIT_ZOMBIE退出狀態。它佔用的所有內存就是內核棧、thread_info結構和task_struct結構。此時進程存在的唯一目的就是向它的父進程提供信息。父進程檢索到信息後,或者通知內核那是無關的信息後,由進程所持有的剩餘內存纔會被釋放,歸還給系統使用。這也就是爲什麼如果父進程沒有回收子進程結束的信息產生殭屍進程會造成資源浪費。
刪除進程描述符

進程終結時所需的清理工作和進程描述符的刪除被分開執行。在父進程獲得已終結的子進程的信息後,或者通知內核它並不關注那些信息後,子進程的task_struct結構才被釋放。

#include<sys/wait.h>
pid_t wait(int *status);
1.如果調用進程並無之前未被等待的子進程終止,則調用一直阻塞,直至某個子進程終止。如果調用時已有子進程終止,wait()則立即返回
2.如果status非空,那麼關於子進程如何終止的信息則會通過status指向的整型變量返回。
3.返回值
成功時返回終止子進程的ID
出錯時,wait返回-1,可能的錯誤原因之一是調用進程並無之前未被等待的子進程,此時會將errno置爲ECHILD。

/*
如果父進程已經創建了多個子進程,使用wait()將無法等待某個特定子進程的完成,只能按順序等待下一個子進程的終止。
如果沒有子進程退出,wait()總是保持阻塞。有時候會希望執行非阻塞的等待:是否有子進程退出,立判可知。
使用wait()只能發現那些已經終止的子進程。對於子進程因某個信號(如SIGTOP或SIGINT)而停止,或是已停止子進程收到SIGCONT信號後恢復執行的情況就無能爲力了。
*/
pid_t waitpid(pid_t pid, int *status, int options);
參數pid
如果pid大於0,表示等待進程ID爲pid的子進程。
如果pid等於0,則等待與調用進程(父進程)同一個進程組的所有子進程。
如果pid小於-1,則會等待進程組標識符與pid絕對值相等的所有子進程。
如果pid等於-1.則等待任意子進程。wait(&status)waitpid(-1, &status, 0)相同。

參數options

  • WUNTRACED: 除了返回子進程的信息外,還返回因信號而停止的子進程的信息。
  • WCONTINUED: 返回那些因收到SIGCONT信號而恢復執行的已停止的子進程的狀態信息。
  • WNOHANG: 如果參數pid所指定的子進程並未發生狀態改變,則立即返回,而不會阻塞,亦即poll(輪詢)。在這種情況下,waitpid返回0.如果調用進程並無與pid匹配的子進程,則waitpid會報錯,將錯誤號置爲ECHILD。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章