Linux下如何避免殭屍進程的產生
1. 什麼是殭屍進程
比如進程採用exit()退出的時候,操作系統會進行一些列的處理工作,包括關閉打開的文件描述符、佔用的內存等等,但是,操作系統也會爲該進程保留少量的信息,比如進程ID號等信息,因而佔用了系統的資源。在一種極端的情況下,檔殭屍進程過多的時候,佔用了大量的進程ID,系統將無法產生新的進程,相當於系統的資源被耗盡。
所以,避免殭屍進程的產生具有極其重要的意義。一般來講避免殭屍進程主要包含以下幾種方法:
- 父進程使用wait()或者waitpid()之類的函數等待子進程退出
- 父進程先產生一個子進程,然後子進程再產生一個孫子進程,子進程在孫子進程之前退出。
- 使用信號函數sigaction爲SIGCHLD設置wait處理函數。
2. wait()函數
wait()函數的使用最爲簡單,源代碼如圖所示:
父進程創建子進程後30s調用wait()函數,等待子進程退出,回收子進程的資源,這也意味着子進程將會成爲殭屍進程30s-5s=25s。
運行該程序後,打開終端,查看進程狀態,該圖顯示的有一個進程的標誌爲Z,表示該進程爲殭屍進程(Zombie)。
當父進程調用wait()函數後,子進程的資源被回收,殭屍進程的標誌被去掉了.如下圖所示:
3. 兩次fork()創建孫子進程
2中的方法比較簡潔,但是有個問題就是子進程如果處理的時間比較長的話,主進程會被掛起。比如:
- socket()
- bind()
- listen()
- while(1)
- {
- accept()
- if(fork()==0)
- {
- while(1)
- {
- read()
- process()
- write()
- }
- close()
- exit
- }
- //wait()///<如果這裏父進程進行wait()操作,則很有可能再此處掛起,而如果不進行wait()操作,則此處又產生了殭屍進程。
- }
對於這樣的情況可以採取連續fork()兩次的方法。簡而言之,首先父進程首先創建子進程,子進程創建孫子進程,由孫子進程處理事務,
而子進程再創建完孫子進程後,就退出。此時,孫子進程的父進程,也就是子進程退出了,因此孫子進程變爲了一個孤兒進程,Linux進程處理
孤兒的進程的方式,是init進程接管孤兒進程,而init進程的子進程不會成爲殭屍進程。
所以上述的僞代碼可以寫爲:
- if(fork() ==0 )
- {
- if(fork()==0)
- {
- /*孫子進程在這裏處理事務*/
- process();
- close()
- exit()
- }
- else
- {
- /*子進程再這裏退出,使得孫子進程成爲init進程的兒子,從而避免殭屍進程的產生*/
- close()
- exit()///<子進程在此退出
- }
- }
- else
- {
- ///<爺爺進程進入下一輪的處理。
- }
4. 採用信號量處理函數
還有一種方法就是採用信號量處理函數來處理這種情況,該過程的代碼可以表示爲:
- #include
- #include
- #include
- #include
- #include
- #include
- int num_clients = 0;
- int dead_clients = 0;
- void sig_chld_handler(int sig) {
- pid_t pid;
- if (sig == SIGCHLD) {
- pid = wait(NULL);
- printf("A child dead, current child number: %d, id: %d/n", ++dead_clients, pid);
- }
- }
- int main(int argc, char **argv) {
- pid_t pid;
- signal(SIGCHLD, sig_chld_handler);
- for (int i = 0; i < 30; i++) {
- if ((pid = fork()) == 0) {
- exit(0);
- } else if (pid > 0) {
- printf("A child created, current child number: %d, id: %d/n", ++num_clients, pid);
- }
- }
- sleep(10);
- return 0;
- }
父進程首先註冊一個信號處理函數signal(SIGCHLD, sig_chld_handler),然後每當子進程退出的時候父進程都會受到SIGCHLD信號,
觸發sig_chld_handler()函數,調用wait()函數等待子進程的退出。
5.總結
上述三種方法都有好處,個人比較傾向於連續兩次fork(),這種方式我個人認爲是最爲簡潔的方式。