自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Linux后臺開發(fā)中避免僵尸進(jìn)程的方法總結(jié)

開發(fā) 前端
一般情況下,程序調(diào)用exit(包括_exit和_Exit,它們的區(qū)別這里不做解釋),它的絕大多數(shù)內(nèi)存和相關(guān)的資源已經(jīng)被內(nèi)核釋放掉,但是在進(jìn)程表中這個(gè)進(jìn)程項(xiàng)(entry)還保留著(進(jìn)程ID,退出狀態(tài),占用的資源等等)

 一、什么是僵死進(jìn)程?

一般情況下,程序調(diào)用exit(包括_exit和_Exit,它們的區(qū)別這里不做解釋),它的絕大多數(shù)內(nèi)存和相關(guān)的資源已經(jīng)被內(nèi)核釋放掉,但是在進(jìn)程表中這個(gè)進(jìn)程項(xiàng)(entry)還保留著(進(jìn)程ID,退出狀態(tài),占用的資源等等),你可能會問,為什么這么麻煩,直接釋放完資源不就行了嗎?這是因?yàn)橛袝r(shí)它的父進(jìn)程想了解它的退出狀態(tài)。在子進(jìn)程退出但還未被其父進(jìn)程“收尸”之前,該子進(jìn)程就是僵死進(jìn)程,或者僵尸進(jìn)程。如果父進(jìn)程先于子進(jìn)程去世,那么子進(jìn)程將被init進(jìn)程收養(yǎng),這個(gè)時(shí)候init就是這個(gè)子進(jìn)程的父進(jìn)程。

所以一旦出現(xiàn)父進(jìn)程長期運(yùn)行,而又沒有顯示調(diào)用wait或者waitpid,同時(shí)也沒有處理SIGCHLD信號,這個(gè)時(shí)候init進(jìn)程就沒有辦法來替子進(jìn)程收尸,這個(gè)時(shí)候,子進(jìn)程就真的成了“僵尸”了。

二、僵死進(jìn)程與孤兒進(jìn)程的區(qū)別?

回答這個(gè)問題很簡單,就是爸爸(父進(jìn)程)和兒子(子進(jìn)程)誰先死的問題!

如果當(dāng)兒子還在世的時(shí)候,爸爸去世了,那么兒子就成孤兒了,這個(gè)時(shí)候兒子就會被init收養(yǎng),換句話說,init進(jìn)程充當(dāng)了兒子的爸爸,所以等到兒子去世的時(shí)候,就由init進(jìn)程來為其收尸。

如果當(dāng)爸爸還活著的時(shí)候,兒子死了,這個(gè)時(shí)候如果爸爸不給兒子收尸,那么兒子就會變成僵尸進(jìn)程。

三、僵死進(jìn)程的危害?

  1. 僵死進(jìn)程的PID還占據(jù)著,意味著海量的子進(jìn)程會占據(jù)滿進(jìn)程表項(xiàng),會使后來的進(jìn)程無法fork.
  2. 僵死進(jìn)程的內(nèi)核棧無法被釋放掉(1K 或者 2K大小),為啥會留著它的內(nèi)核棧,因?yàn)樵跅5淖畹投?,有著thread_info結(jié)構(gòu),它包含著 struct_task 結(jié)構(gòu),這里面包含著一些退出信息。

四、避免僵死進(jìn)程的方法

網(wǎng)上搜了下,總結(jié)有三種方方法:

① 程序中顯示的調(diào)用signal(SIGCHLD, SIG_IGN)來忽略SIGCHLD信號,這樣子進(jìn)程結(jié)束后,由內(nèi)核來wai和釋放資源

② fork兩次,第一次fork的子進(jìn)程在fork完成后直接退出,這樣第二次fork得到的子進(jìn)程就沒有爸爸了,它會自動(dòng)被老祖宗init收養(yǎng),init會負(fù)責(zé)釋放它的資源,這樣就不會有“僵尸”產(chǎn)生了

③ 對子進(jìn)程進(jìn)行wait,釋放它們的資源,但是父進(jìn)程一般沒工夫在那里守著,等著子進(jìn)程的退出,所以,一般使用信號的方式來處理,在收到SIGCHLD信號的時(shí)候,在信號處理函數(shù)中調(diào)用wait操作來釋放他們的資源。

五、對每個(gè)避免僵死進(jìn)程方法的解析與總結(jié)

首先我們讓我們來看一個(gè)生成僵尸進(jìn)程的程序zombie.c如下:

  1. #include <stdio.h>   
  2. #include <stdlib.h>  #include <unistd.h>    int main(int argc, const char *argv[])   
  3. {      int i;   
  4.     pid_t pid;        for (i = 0; i < 10; i++) {   
  5.         if ((pid = fork()) == 0)    /* child */   
  6.             _exit(0);   
  7.     }      sleep(10);   
  8.       exit(EXIT_SUCCESS);  }   

運(yùn)行程序,在10s睡眠期間使用ps查看進(jìn)程,你會發(fā)現(xiàn)有10個(gè)標(biāo)記為“defunct”的僵尸進(jìn)程:

linux后臺開發(fā)中避免僵尸進(jìn)程的方法總結(jié)

接下來看第一種方法,程序avoid_zombie1.c如下:

  1. #include <stdio.h>   
  2. #include <stdlib.h>  #include <signal.h>  #include <unistd.h>  #include <errno.h>    int main(int argc, const char *argv[])   
  3. {      pid_t pid;        if (SIG_ERR == signal(SIGCHLD, SIG_IGN)) {   
  4.         perror("signal error");   
  5.         _exit(EXIT_FAILURE);      }        while (1) {   
  6.         if ((pid = fork()) == 0)    /* child */   
  7.             _exit(0);   
  8.     }        exit(EXIT_SUCCESS);  }   

程序運(yùn)行期間通過ps命令的確沒有發(fā)現(xiàn)僵尸進(jìn)程的存在。

在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.

意思是說盡管系統(tǒng)對信號SIGCHLD的默認(rèn)處理就是“ignore”,但是顯示的設(shè)置成SIG_IGN的處理方式在在這里會表現(xiàn)不同的處理方式(即子進(jìn)程結(jié)束后,資源由系統(tǒng)自動(dòng)收回,所以不會產(chǎn)生僵尸進(jìn)程),這是信號SIGCHLD與其他信號的不同之處。

在man文檔中同樣有這樣一段話:

The original POSIX standard left the behavior of setting SIGCHLD to SIG_IGN unspecified. 看來這個(gè)方法不是每個(gè)平臺都使用,尤其在一些老的系統(tǒng)中,兼容性不是很好,所以如果你在寫一個(gè)可移植的程序的話,不推薦使用這個(gè)方法。

第二種方法,即通過兩次fork來避免僵尸進(jìn)程,我們來看一個(gè)例子avoid_zombie2.c:

  1. #include <stdio.h>   
  2. #include <stdlib.h>  #include <signal.h>  #include <unistd.h>  #include <errno.h>    int main(int argc, const char *argv[])   
  3. {      pid_t pid;        while (1) {   
  4.         if ((pid = fork()) == 0) {  /* child */   
  5.             if ((pid = fork()) > 0)   
  6.                 _exit(0);   
  7.             sleep(1);   
  8.             printf("grandchild, parent id = %ld\n",   
  9.                             (long)getppid());              _exit(0);   
  10.         }          if (waitpid(-1, NULL, 0) != pid) {   
  11.             perror("waitpid error");   
  12.             _exit(EXIT_FAILURE);          }      }        exit(EXIT_SUCCESS);  }   

這的確是個(gè)有效的辦法,但是我想這個(gè)方法不適宜網(wǎng)絡(luò)并發(fā)服務(wù)器中,應(yīng)為fork的效率是不高的。

最后來看第三種方法, 也是最通用的方法

先看我們的測試程序avoid_zombie3.c

  1. #include <stdio.h>   
  2. #include <stdlib.h>  #include <errno.h>  #include <string.h>   
  3. #include <libgen.h>  #include <signal.h>  #include <unistd.h>  #include <sys/wait.h>  #include <sys/types.h>      void avoid_zombies_handler(int signo)   
  4. {      pid_t pid;      int exit_status;   
  5.     int saved_errno = errno;   
  6.       while ((pid = waitpid(-1, &exit_status, WNOHANG)) > 0) {   
  7.         /* do nothing */   
  8.     }        errno = saved_errno;  }    int main(int argc, char *argv[])   
  9. {      pid_t pid;      int status;   
  10.     struct sigaction child_act;    
  11.       memset(&child_act, 0, sizeof(struct sigaction));   
  12.     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) {   
  13.         perror("sigaction error");   
  14.         _exit(EXIT_FAILURE);      }        while (1) {   
  15.         if ((pid = fork()) == 0) {  /* child process */   
  16.             _exit(0);   
  17.         } else if (pid > 0) {        /* parent process */   
  18.         }      }            _exit(EXIT_SUCCESS);  }   

首先需要知道三點(diǎn):

1. 當(dāng)某個(gè)信號的信號處理函數(shù)被調(diào)用時(shí),該信號會被操作系統(tǒng)阻塞(默認(rèn)sa_flags不設(shè)置SA_NODEFER標(biāo)志)。

2.當(dāng)某個(gè)信號的信號處理函數(shù)被調(diào)用時(shí),該信號阻塞時(shí),該信號又多次發(fā)生,那么操作系統(tǒng)并不將它們排隊(duì),而是只保留第一次的,后續(xù)的被拋棄。

還有一點(diǎn)我們必須清楚的是

3. wait系列函數(shù)與信號SIGCHLD是沒有任何關(guān)系的,即wait系列函數(shù)并不是信號SIGCHLD驅(qū)動(dòng)的。

這個(gè)時(shí)候,肯定有人有疑問了,既然會丟棄信號,那怎么保證可以收回所有的僵尸進(jìn)程呢?

關(guān)于這個(gè)問題,我們可以這樣來理解,當(dāng)子進(jìn)程結(jié)束時(shí),不管有沒有產(chǎn)生SIGCHLD信號,或者子進(jìn)程產(chǎn)生了SIGCHLD信號,而不管父進(jìn)程有沒有收到SIGCHLD信號,這都與子進(jìn)程已經(jīng)終止這個(gè)事實(shí)無關(guān),就是說,子進(jìn)程終止與信號其實(shí)沒有任何關(guān)系,只是操作系統(tǒng)在子進(jìn)程終止時(shí)會發(fā)送信號SIGCHLD給父進(jìn)程,告之其子進(jìn)程終止的消息,這樣的話,父進(jìn)程就可以做相應(yīng)的操作了。而wait系列函數(shù)的目的就是收回子進(jìn)程終止時(shí)殘留在進(jìn)程列表中的信息,所以任何時(shí)候調(diào)用while ((pid = waitpid(-1, &exit_status, WNOHANG)) > 0)都可以收回所有的僵尸進(jìn)程信息(可以參考下面的程序)。但是這里為什么放在信號處理函數(shù)中處理了,這樣做的原因是:子進(jìn)程什么時(shí)候結(jié)束是個(gè)異步事件,而信號機(jī)制就是用來處理異步事件的,所以當(dāng)子進(jìn)程結(jié)束時(shí),可以迅速的收回其殘余信息,這樣系統(tǒng)中就不會積累大量的僵尸進(jìn)程了。

也可以這樣來理解:系統(tǒng)把所有的僵尸進(jìn)程串在一起形成一個(gè)僵尸進(jìn)程鏈表,而while ((pid = waitpid(-1, &exit_status, WNOHANG)) > 0)就是來清空這個(gè)鏈表的,直到waitpid()返回0,表明已經(jīng)沒有僵尸進(jìn)程了,或者返回-1,表明出錯(cuò)(當(dāng)錯(cuò)誤碼errno為ECHILD的時(shí)候同樣表明已經(jīng)不存在僵尸進(jìn)程了)。

了解了以上知識點(diǎn),就能理解為什么while ((pid = waitpid(-1, &exit_status, WNOHANG)) > 0)能夠回收所有的僵尸進(jìn)程了。

我們可以在上面的信號處理函數(shù)中加入相應(yīng)的打印信息:

  1. static int num1 = 0   
  2. static int num2 = 0;   
  3. void avoid_zombies_handler(int signo)   
  4. {      pid_t pid;   
  5.     int exit_status;   
  6.     int saved_errno = errno;   
  7.       printf("num1 = %d\n", ++num1);   
  8.     while ((pid = waitpid(-1, &exit_status, WNOHANG)) > 0) {   
  9.         printf("num2 = %d\n", ++num2);   
  10.     }        errno = saved_errno;  }   

打印的結(jié)果你會發(fā)現(xiàn),當(dāng)num1遞增1的時(shí)候,即每調(diào)用一次信號處理函數(shù),num2一般會遞增很多,即while循環(huán)了很多次,所以盡管有的SIGCHLD信號被丟棄了,但是我們不用擔(dān)心子進(jìn)程的殘余信息會收不回來。退出while循環(huán)時(shí),證明此時(shí)系統(tǒng)中已經(jīng)沒有僵尸進(jìn)程了,所以退出信號處理函數(shù)后,阻塞的唯一SIGCHLD信號會再次觸發(fā)該信號處理函數(shù),這樣我們就不用擔(dān)心了。我們不防做個(gè)最壞的打算,即之前的信號全部被丟棄了,只有最后一次的SIGCHLD信號被捕獲,從而觸發(fā)了信號處理函數(shù),這樣我們也不用擔(dān)心,因?yàn)閣hile循環(huán)會一次性收回全部的僵尸進(jìn)程信息,只是這次循環(huán)的次數(shù)要多得多罷了,當(dāng)然這只是假設(shè),一般系統(tǒng)不會出現(xiàn)這樣的情況(可以參考本文最后一個(gè)程序事例)。

為了證明wait系統(tǒng)函數(shù)與信號SIGCHLD沒有任何關(guān)系,我們可以做個(gè)簡單的實(shí)驗(yàn),代碼如下:

  1. #include <stdio.h>   
  2. #include <stdlib.h>   
  3. #include <unistd.h>   
  4. #include <sys/wait.h>   
  5. #include <sys/types.h>   
  6.   int main(int argc, char *argv[])  {      int i;      pid_t pid;        for (i = 0; i < 5; i++) {   
  7.         if ((pid = fork()) == 0)    /* child */   
  8.             _exit(0);   
  9.     }   
  10.     sleep(10);   
  11.     while (waitpid(-1, NULL, WNOHANG) > 0) {   
  12.         /* do nothing */      }      sleep(10);        _exit(EXIT_SUCCESS);  }   

以下是打印結(jié)果:

linux后臺開發(fā)中避免僵尸進(jìn)程的方法總結(jié)

可以看到第一次sleep時(shí)系統(tǒng)中積累了5個(gè)僵尸進(jìn)程,第二次sleep時(shí),那5個(gè)僵尸進(jìn)程都被收回了。這個(gè)也明顯的看到了使用信號處理函數(shù)的優(yōu)勢,即可以保證系統(tǒng)不會積累大量的僵尸進(jìn)程,它可以迅速的清理掉系統(tǒng)中的僵尸進(jìn)程。

責(zé)任編輯:張燕妮 來源: 今日頭條
相關(guān)推薦

2024-02-05 18:23:23

父進(jìn)程應(yīng)用程序程序

2021-11-08 10:30:30

Linux僵尸命令

2021-10-25 12:23:06

Linux僵尸進(jìn)程

2021-11-06 10:17:38

Linux僵尸進(jìn)程

2024-05-23 08:24:11

Android進(jìn)程開發(fā)

2021-09-14 13:25:23

容器pod僵尸進(jìn)程

2022-02-10 15:25:47

LinuxIO優(yōu)化

2017-03-17 16:10:24

linux進(jìn)程后臺

2017-12-15 09:40:47

Linux僵尸進(jìn)程

2013-04-07 17:18:16

僵尸網(wǎng)絡(luò)變色龍

2022-07-28 11:09:44

Linux優(yōu)化IO

2010-03-31 14:36:50

Oracle進(jìn)程結(jié)構(gòu)

2013-03-15 10:48:14

創(chuàng)業(yè)創(chuàng)業(yè)公司

2015-05-05 10:51:32

php頁面跳轉(zhuǎn)方法

2011-09-16 14:53:55

WLAN無線干擾

2024-08-26 08:39:26

PHP孤兒進(jìn)程僵尸進(jìn)程

2010-06-28 14:52:30

cron進(jìn)程

2017-02-24 15:28:33

Android內(nèi)存溢出方法總結(jié)

2021-11-01 12:13:53

Linux僵尸進(jìn)程

2015-06-16 09:57:53

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號