Linux殭屍程式處置
導讀 | 一般情況下,程式呼叫exit(包括_exit和_Exit,它們的區別這裡不做解釋),它的絕大多數記憶體和相關的資源已經被核心釋放掉,但是在程式表中這個程式項(entry)還保留著(程式ID,退出狀態,佔用的資源等等) |
一般情況下,程式呼叫exit(包括_exit和_Exit,它們的區別這裡不做解釋),它的絕大多數記憶體和相關的資源已經被核心釋放掉,但是在程式表中這個程式項(entry)還保留著(程式ID,退出狀態,佔用的資源等等),你可能會問,為什麼這麼麻煩,直接釋放完資源不就行了嗎?這是因為有時它的父程式想了解它的退出狀態。在子程式退出但還未被其父程式“收屍”之前,該子程式就是僵死程式,或者殭屍程式。如果父程式先於子程式去世,那麼子程式將被init程式收養,這個時候init就是這個子程式的父程式。
所以一旦出現父程式長期執行,而又沒有顯示呼叫wait或者waitpid,同時也沒有處理SIGCHLD訊號,這個時候init程式就沒有辦法來替子程式收屍,這個時候,子程式就真的成了“殭屍”了。
回答這個問題很簡單,就是爸爸(父程式)和兒子(子程式)誰先死的問題!
如果當兒子還在世的時候,爸爸去世了,那麼兒子就成孤兒了,這個時候兒子就會被init收養,換句話說,init程式充當了兒子的爸爸,所以等到兒子去世的時候,就由init程式來為其收屍。
如果當爸爸還活著的時候,兒子死了,這個時候如果爸爸不給兒子收屍,那麼兒子就會變成殭屍程式。
- 僵死程式的PID還佔據著,意味著海量的子程式會佔據滿程式表項,會使後來的程式無法fork.
- 僵死程式的核心棧無法被釋放掉(1K 或者 2K大小),為啥會留著它的核心棧,因為在棧的最低端,有著thread_info結構,它包含著 struct_task 結構,這裡麵包含著一些退出資訊。
網上搜了下,總結有三種方方法:
- ① 程式中顯示的呼叫signal(SIGCHLD, SIG_IGN)來忽略SIGCHLD訊號,這樣子程式結束後,由核心來wai和釋放資源
- ② fork兩次,第一次fork的子程式在fork完成後直接退出,這樣第二次fork得到的子程式就沒有爸爸了,它會自動被老祖宗init收養,init會負責釋放它的資源,這樣就不會有“殭屍”產生了
- ③ 對子程式進行wait,釋放它們的資源,但是父程式一般沒工夫在那裡守著,等著子程式的退出,所以,一般使用訊號的方式來處理,在收到SIGCHLD訊號的時候,在訊號處理函式中呼叫wait操作來釋放他們的資源。
首先我們讓我們來看一個生成殭屍程式的程式zombie.c如下:
#include #include #include int main(int argc, const char *argv[]) { int i; pid_t pid; for (i = 0; i < 10; i++) { if ((pid = fork()) == 0) /* child */ _exit(0); } sleep(10); exit(EXIT_SUCCESS); }
執行程式,在10s睡眠期間使用ps檢視程式,你會發現有10個標記為“defunct”的殭屍程式:
接下來看第一種方法,程式avoid_zombie1.c如下:
#include #include #include #include #include int main(int argc, const char *argv[]) { pid_t pid; if (SIG_ERR == signal(SIGCHLD, SIG_IGN)) { perror("signal error"); _exit(EXIT_FAILURE); } while (1) { if ((pid = fork()) == 0) /* child */ _exit(0); } exit(EXIT_SUCCESS); }
程式執行期間透過ps 的確沒有發現殭屍程式的存在。
在man文件中有這段話:
Note that even though the default disposition of SIGCHLD is "ignore", explicitly setting the disposition to SIG_IGN results in different treatment of zombie process children.
意思是說盡管系統對訊號SIGCHLD的預設處理就是“ignore”,但是顯示的設定成SIG_IGN的處理方式在在這裡會表現不同的處理方式(即子程式結束後,資源由系統自動收回,所以不會產生殭屍程式),這是訊號SIGCHLD與其他訊號的不同之處。
在man文件中同樣有這樣一段話:
The original POSIX standard left the behavior of setting SIGCHLD to SIG_IGN unspecified. 看來這個方法不是每個平臺都使用,尤其在一些老的系統中,相容性不是很好,所以如果你在寫一個可移植的程式的話,不推薦使用這個方法。
第二種方法,即透過兩次fork來避免殭屍程式,我們來看一個例子avoid_zombie2.c:
#include #include #include #include #include int main(int argc, const char *argv[]) { pid_t pid; while (1) { if ((pid = fork()) == 0) { /* child */ if ((pid = fork()) > 0) _exit(0); sleep(1); printf("grandchild, parent id = %ld\n", (long)getppid()); _exit(0); } if (waitpid(-1, NULL, 0) != pid) { perror("waitpid error"); _exit(EXIT_FAILURE); } } exit(EXIT_SUCCESS); }
這的確是個有效的辦法,但是我想這個方法不適宜網路併發伺服器中,應為fork的效率是不高的。
最後來看第三種方法, 也是最通用的方法
先看我們的測試程式avoid_zombie3.c
#include #include #include #include #include #include #include #include <sys/wait.h> #include <sys/types.h> void avoid_zombies_handler(int signo) { pid_t pid; int exit_status; int saved_errno = errno; while ((pid = waitpid(-1, &exit_status, WNOHANG)) > 0) { /* do nothing */ } errno = saved_errno; } int main(int argc, char *argv[]) { pid_t pid; int status; struct sigaction child_act; memset(&child_act, 0, sizeof(struct sigaction)); child_act.sa_handler = avoid_zombies_handler; child_act.sa_flags = SA_RESTART | SA_NOCLDSTOP; sigemptyset(&child_act.sa_mask); if (sigaction(SIGCHLD, &child_act, NULL) == -1) { perror("sigaction error"); _exit(EXIT_FAILURE); } while (1) { if ((pid = fork()) == 0) { /* child process */ _exit(0); } else if (pid > 0) { /* parent process */ } } _exit(EXIT_SUCCESS); }
首先需要知道三點:
- 1. 當某個訊號的訊號處理函式被呼叫時,該訊號會被作業系統阻塞(預設sa_flags不設定SA_NODEFER標誌)。
- 2.當某個訊號的訊號處理函式被呼叫時,該訊號阻塞時,該訊號又多次發生,那麼作業系統並不將它們排隊,而是隻保留第一次的,後續的被拋棄。
- 3. wait系列函式與訊號SIGCHLD是沒有任何關係的,即wait系列函式並不是訊號SIGCHLD驅動的。
這個時候,肯定有人有疑問了,既然會丟棄訊號,那怎麼保證可以收回所有的殭屍程式呢?
關於這個問題,我們可以這樣來理解,當子程式結束時,不管有沒有產生SIGCHLD訊號,或者子程式產生了SIGCHLD訊號,而不管父程式有沒有收到SIGCHLD訊號,這都與子程式已經終止這個事實無關,就是說,子程式終止與訊號其實沒有任何關係,只是作業系統在子程式終止時會傳送訊號SIGCHLD給父程式,告之其子程式終止的訊息,這樣的話,父程式就可以做相應的操作了。而wait系列函式的目的就是收回子程式終止時殘留在程式列表中的資訊,所以任何時候呼叫while ((pid = waitpid(-1, &exit_status, WNOHANG)) > 0)都可以收回所有的殭屍程式資訊(可以參考下面的程式)。但是這裡為什麼放在訊號處理函式中處理了,這樣做的原因是:子程式什麼時候結束是個非同步事件,而訊號機制就是用來處理非同步事件的,所以當子程式結束時,可以迅速的收回其殘餘資訊,這樣系統中就不會積累大量的殭屍程式了。
也可以這樣來理解:系統把所有的殭屍程式串在一起形成一個殭屍程式連結串列,而while ((pid = waitpid(-1, &exit_status, WNOHANG)) > 0)就是來清空這個連結串列的,直到waitpid()返回0,表明已經沒有殭屍程式了,或者返回-1,表明出錯(當錯誤碼errno為ECHILD的時候同樣表明已經不存在殭屍程式了)。
瞭解了以上知識點,就能理解為什麼while ((pid = waitpid(-1, &exit_status, WNOHANG)) > 0)能夠回收所有的殭屍程式了。
我們可以在上面的訊號處理函式中加入相應的列印資訊:
static int num1 = 0 static int num2 = 0; void avoid_zombies_handler(int signo) { pid_t pid; int exit_status; int saved_errno = errno; printf("num1 = %d\n", ++num1); while ((pid = waitpid(-1, &exit_status, WNOHANG)) > 0) { printf("num2 = %d\n", ++num2); } errno = saved_errno; }
列印的結果你會發現,當num1遞增1的時候,即每呼叫一次訊號處理函式,num2一般會遞增很多,即while迴圈了很多次,所以儘管有的SIGCHLD訊號被丟棄了,但是我們不用擔心子程式的殘餘資訊會收不回來。退出while迴圈時,證明此時系統中已經沒有殭屍程式了,所以退出訊號處理函式後,阻塞的唯一SIGCHLD訊號會再次觸發該訊號處理函式,這樣我們就不用擔心了。我們不防做個最壞的打算,即之前的訊號全部被丟棄了,只有最後一次的SIGCHLD訊號被捕獲,從而觸發了訊號處理函式,這樣我們也不用擔心,因為while迴圈會一次性收回全部的殭屍程式資訊,只是這次迴圈的次數要多得多罷了,當然這只是假設,一般系統不會出現這樣的情況(可以參考本文最後一個程式事例)。
為了證明wait系統函式與訊號SIGCHLD沒有任何關係,我們可以做個簡單的實驗,程式碼如下:
#include #include #include #include <sys/wait.h> #include <sys/types.h> int main(int argc, char *argv[]) { int i; pid_t pid; for (i = 0; i < 5; i++) { if ((pid = fork()) == 0) /* child */ _exit(0); } sleep(10); while (waitpid(-1, NULL, WNOHANG) > 0) { /* do nothing */ } sleep(10); _exit(EXIT_SUCCESS); }
以下是列印結果:
可以看到第一次sleep時系統中積累了5個殭屍程式,第二次sleep時,那5個殭屍程式都被收回了。這個也明顯的看到了使用訊號處理函式的優勢,即可以保證系統不會積累大量的殭屍程式,它可以迅速的清理掉系統中的殭屍程式。
原文來自:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2728680/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Linux 殭屍程式Linux
- 檢視 Linux 殭屍程式Linux
- 殭屍程式
- Linux中殭屍程式是什麼意思?怎麼檢視殭屍程式?Linux
- Linux 中殭屍程式詳解Linux
- Linux 系統中殭屍程式Linux
- Linux如何殺掉殭屍程式Linux
- Linux系統殭屍程式詳解Linux
- 什麼是殭屍程式,如何找到並殺掉殭屍程式?
- 什麼是殭屍程式以及如何處理
- fork和殭屍程式
- 【系統】 殭屍程式
- 殺死殭屍程式
- 殭屍程式,孤兒程式
- 殭屍程式和孤兒程式
- Linux 效能優化之 CPU 篇 ----- 殭屍程式Linux優化
- Linux程式管理、程式建立、執行緒實現、殭屍程式Linux執行緒
- 關於LINUX殭屍程式的出現和原理Linux
- Perl程式:殭屍程式和孤兒程式
- 子程式、孤兒程式,殭屍程式, init程式
- Go Exec 殭屍與孤兒程式Go
- 檢視並殺死殭屍程式
- 物聯網裝置殭屍網路趨勢分析
- Linux上的殭屍跑得比Windows快LinuxWindows
- 第十九篇:處理殭屍程式的兩種經典方法
- 物聯網教程Linux系統程式設計——特殊程式之殭屍程式Linux程式設計
- 盤點:網際網路上無處不在的"殭屍"
- JaCoCo助您毀滅線上殭屍程式碼
- linux系統程式設計之程式(三):程式複製fork,孤兒程式,殭屍程式Linux程式設計
- iOS殭屍物件之研究iOS物件
- Linux 幹掉狀態為Z的殭屍程序Linux
- Linux 下 popen 函式引起的殭屍程式 defunct 以及解決辦法Linux函式
- 什麼是殭屍網路
- Unity 植物大戰殭屍(一)Unity
- 孤兒程序和殭屍程序
- Chalubo殭屍網路來襲 IOT裝置或將受到DDoS攻擊
- hp-unix 殭屍程式導致系統崩潰
- 第一個能在裝置重啟後繼續存活的殭屍網路