linux 系統中僵屍程序和現實中僵屍(雖然我也沒見過)類似,雖然已經死了,但是由于沒人給它們收屍,還能四處走動。僵屍程序指的是那些雖然已經終止的程序,但仍然保留一些資訊,等待其父程序為其收屍。
<a target="_blank"></a>
如果一個程序在其終止的時候,自己就回收所有配置設定給它的資源,系統就不會産生所謂的僵屍程序了。那麼我們說一個程序終止之後,還保留哪些資訊?為什麼終止之後還需要保留這些資訊呢?
一個程序終止的方法很多,程序終止後有些資訊對于父程序和核心還是很有用的,例如程序的id号、程序的退出狀态、程序運作的cpu時間等。是以程序 在終止時,回收所有核心配置設定給它的記憶體、關閉它打開的所有檔案等等,但是還會保留以上極少的資訊,以供父程序使用。父程序可以使用 wait/waitpid 等系統調用來為子程序收拾,做一些收尾工作。
是以,一個僵屍程序産生的過程是:父程序調用fork建立子程序後,子程序運作直至其終止,它立即從記憶體中移除,但程序描述符仍然保留在記憶體中(程序描述符占有極少的記憶體空間)。子程序的狀态變成exit_zombie,并且向父程序發送sigchld 信号,父程序此時應該調用 wait() 系 統調用來擷取子程序的退出狀态以及其它的資訊。在 wait 調用之後,僵屍程序就完全從記憶體中移除。是以一個僵屍存在于其終止到父程序調用 wait 等函數這個時間的間隙,一般很快就消失,但如果程式設計不合理,父程序從不調用 wait 等系統調用來收集僵屍程序,那麼這些程序會一直存在記憶體中。
在 linux 下,我們可以使用 ps 等指令檢視系統中僵屍程序,僵屍程序的狀态标記為‘z’:

https://dn-linuxcn.qbox.me/data/attachment/album/201404/09/145023nsfw6cj420uo6ofo.png
根據上面的描述,我們很容易去寫一個程式來産生僵屍程序,如下代碼:
#include #include <sys/types.h> 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 等系統調用函數,是以在子程序退出之後變成僵屍程序,父程序并沒有為其去收屍。我們使用下面指令編譯運作該程序,然後檢視系統中程序狀态:
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]
從上面可以看出,系統中多了一個僵屍程序。但如果等父程序睡眠醒來退出之後,我們再次檢視系統程序資訊,發現剛才的僵屍程序不見了。
after sleeping, and exit! guohailin@guohailin:~/documents$ ps aux | grep -w 'z' 1000 4400 0.0 0.0 0 0 ? z 10月16 0:00 [fcitx]
這是為什麼呢?父程序到死都也沒有為其子程序收屍呀,怎麼父程序退出之後,那個僵屍程序就消失了呢?難道父程序在退出時會為子程序收拾嗎?其實不 然....真正的原因是:父程序死掉之後,其所有子程序過繼給 init 程序,init 程序成為該僵屍程序的新程序,init 程序會周期性地去調用 wait 系統調用來清除它的僵屍孩子。是以,你會發現上面例子中父程序死掉之後,僵屍程序也跟着消失,其實是 init 程序為其收屍的!
不能使用 kill 後接 sigkill 信号這樣的指令像殺死普通程序一樣殺死僵屍程序,因為僵屍程序是已經死掉的程序,它不能再接收任何信号。事實上,如果系統中僵屍程序并不多的話,我們也無需去消除它們,少數的僵屍程序并不會對系統的性能有什麼影響。
那麼在程式設計時,如果能避免系統中大量産生僵屍程序呢?根據上面描述的,子程序在終止時會向父程序發 sigchld 信号,linux 預設是忽略該信号的,我們可以顯示安裝該信号,在信号處理函數中調用 wait 等函數來為其收屍,這樣就能避免僵屍程序長期存在于系統中了。示例代碼如下:
#include <sys/wait.h> 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; /* 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"); else if(c_pid == 0) printf("in child process, and exit now\n"); else printf("fork failed!\n"); }
<a href="http://en.wikipedia.org/wiki/zombie_process" target="_blank">http://en.wikipedia.org/wiki/zombie_process</a>
<a href="http://simplestcodings.blogspot.com/2010/10/story-of-zombie-process.html" target="_blank">http://simplestcodings.blogspot.com/2010/10/story-of-zombie-process.html</a>
<a href="http://www.howtogeek.com/119815/" target="_blank">http://www.howtogeek.com/119815/</a>
原文釋出時間為:2013-10-20
本文來自雲栖社群合作夥伴“linux中國”