Linux 系統中殭屍程式和現實中殭屍(雖然我也沒見過)類似,雖然已經死了,但是由於沒人給它們收屍,還能四處走動。殭屍程式指的是那些雖然已經終止的程式,但仍然保留一些資訊,等待其父程式為其收屍。
殭屍程式如何產生的?
如果一個程式在其終止的時候,自己就回收所有分配給它的資源,系統就不會產生所謂的殭屍程式了。那麼我們說一個程式終止之後,還保留哪些資訊?為什麼終止之後還需要保留這些資訊呢?
一個程式終止的方法很多,程式終止後有些資訊對於父程式和核心還是很有用的,例如程式的ID號、程式的退出狀態、程式執行的CPU時間等。因此程式在終止時,回收所有核心分配給它的記憶體、關閉它開啟的所有檔案等等,但是還會保留以上極少的資訊,以供父程式使用。父程式可以使用 wait/waitpid 等系統呼叫來為子程式收拾,做一些收尾工作。
因此,一個殭屍程式產生的過程是:父程式呼叫fork建立子程式後,子程式執行直至其終止,它立即從記憶體中移除,但程式描述符仍然保留在記憶體中(程式描述符佔有極少的記憶體空間)。子程式的狀態變成EXIT_ZOMBIE
,並且向父程式傳送SIGCHLD 訊號,父程式此時應該呼叫 wait()
系統呼叫來獲取子程式的退出狀態以及其它的資訊。在 wait 呼叫之後,殭屍程式就完全從記憶體中移除。因此一個殭屍存在於其終止到父程式呼叫 wait 等函式這個時間的間隙,一般很快就消失,但如果程式設計不合理,父程式從不呼叫 wait 等系統呼叫來收集殭屍程式,那麼這些程式會一直存在記憶體中。
在 Linux 下,我們可以使用 ps 等命令檢視系統中殭屍程式,殭屍程式的狀態標記為‘Z’:
產生一個殭屍程式
根據上面的描述,我們很容易去寫一個程式來產生殭屍程式,如下程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include #include int main() { //fork a child process pid_t pid = fork(); if (pid > 0) //parent process { printf("in parent process, sleep for one miniute...zZ...n"); sleep(60); printf("after sleeping, and exit!n"); } else if (pid == 0) { //child process exit, and to be a zombie process printf("in child process, and exit!n"); exit(0); } return 0; } |
父程式並沒有寫 wait 等系統呼叫函式,因此在子程式退出之後變成殭屍程式,父程式並沒有為其去收屍。我們使用下面命令編譯執行該程式,然後檢視系統中程式狀態:
1 2 3 4 5 6 7 8 9 10 |
guohailin@guohailin:~/Documents$ gcc zombie.c -o zombie guohailin@guohailin:~/Documents$ ./zombie in parent process, sleep for one miniute...zZ... in child process, and exit! # 開啟另一個終端: guohailin@guohailin:~$ ps aux | grep -w 'Z' 1000 2211 1.2 0.0 0 0 ? Z 13:24 6:53 [chromium-browse] 1000 4400 0.0 0.0 0 0 ? Z 10月16 0:00 [fcitx] 1000 10871 0.0 0.0 0 0 pts/4 Z+ 22:32 0:00 [zombie] |
從上面可以看出,系統中多了一個殭屍程式。但如果等父程式睡眠醒來退出之後,我們再次檢視系統程式資訊,發現剛才的殭屍程式不見了。
1 2 3 4 5 6 7 |
guohailin@guohailin:~/Documents$ ./zombie in parent process, sleep for one miniute...zZ... in child process, and exit! after sleeping, and exit! guohailin@guohailin:~/Documents$ ps aux | grep -w 'Z' 1000 2211 1.2 0.0 0 0 ? Z 13:24 6:53 [chromium-browse] 1000 4400 0.0 0.0 0 0 ? Z 10月16 0:00 [fcitx] |
這是為什麼呢?父程式到死都也沒有為其子程式收屍呀,怎麼父程式退出之後,那個殭屍程式就消失了呢?難道父程式在退出時會為子程式收拾嗎?其實不然….真正的原因是:父程式死掉之後,其所有子程式過繼給 init 程式,init 程式成為該殭屍程式的新程式,init 程式會週期性地去呼叫 wait 系統呼叫來清除它的殭屍孩子。因此,你會發現上面例子中父程式死掉之後,殭屍程式也跟著消失,其實是 init 程式為其收屍的!
怎樣避免殭屍程式的產生
不能使用 kill 後接 SIGKILL
訊號這樣的命令像殺死普通程式一樣殺死殭屍程式,因為殭屍程式是已經死掉的程式,它不能再接收任何訊號。事實上,如果系統中殭屍程式並不多的話,我們也無需去消除它們,少數的殭屍程式並不會對系統的效能有什麼影響。
那麼在程式設計時,如果能避免系統中大量產生殭屍程式呢?根據上面描述的,子程式在終止時會向父程式發 SIGCHLD 訊號,Linux 預設是忽略該訊號的,我們可以顯示安裝該訊號,在訊號處理函式中呼叫 wait 等函式來為其收屍,這樣就能避免殭屍程式長期存在於系統中了。示例程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
#include #include #include #include #include sig_atomic_t child_exit_status; void clean_up_child_process(int signal_num) { /* clean up child process */ int status; wait (&status); /* store its exit status in a global variable */ child_exit_status = status; } int main() { /* handle SIGCHLD by calling clean_up_child_process */ struct sigaction sigchild_action; memset(&sigchild_action, 0, sizeof(sigchild_action)); sigchild_action.sa_handler = &clean_up_child_process; sigaction(SIGCHLD, &sigchild_action, NULL); /* fork a child, and let the child process dies before parent */ pid_t c_pid; c_pid = fork(); if (c_pid > 0) { printf("in parent process, and sleep for on mininute...zZ...n"); sleep(60); } else if(c_pid == 0) { printf("in child process, and exit nown"); exit(0); } else { printf("fork failed!n"); } return 0; } |