也談殭屍進程

來源:互聯網
上載者:User
一. 何為殭屍進程

殭屍進程 zombie 或 defunct ,ps , top 命令可以看到。 說的是處於“僵死”狀態的進程。 這樣的進程已經死亡,但仍然以某種方式存活著。說其已經死亡,是因為其資源(記憶體、外設連結等)已經釋放,其無法也絕不會再次運行。說其存活著,是因為系統進程表中仍然存在該進程描述符。

看看Linux定義的進程狀態: include/linux/sched.h

/* * Task state bitmask. NOTE! These bits are also * encoded in fs/proc/array.c: get_task_state(). * * We have two separate sets of flags: task->state * is about runnability, while task->exit_state are * about the task exiting. Confusing, but this way * modifying one set can't modify the other one by * mistake. */#define TASK_RUNNING            0#define TASK_INTERRUPTIBLE      1#define TASK_UNINTERRUPTIBLE    2#define __TASK_STOPPED          4#define __TASK_TRACED           8/* in tsk->exit_state */#define EXIT_ZOMBIE             16#define EXIT_DEAD              32/* in tsk->state again */#define TASK_DEAD               64#define TASK_WAKEKILL       128#define TASK_WAKING       256

看到編號16的 EXIT_ZOMBIE 了吧, 沒錯, 僵死本來就是進程的一個狀態, 所以出現殭屍進程也就不足為奇的了。

二. 如何產生殭屍進程

那麼,殭屍進程又是如何產生的呢?這就得從Linux系統進程的建立和銷毀方式說起了。進程的建立就如人的出生,每個人都一樣,不存在生的偉大隻說。但銷毀就不同了,要給其處理後事,這個也很像人類,不同的人死後處理後事複雜度是有很大差距的。

一個進程的銷毀需要經過兩個階段:

1. 進程終止(main 函數中return 或 程式執行 exit) 或被殺死(訊號 SIGTERM,SIGKILL)

2. 該進程的父進程在子進程終止時必須調用或已經調用wait4 (wait, waitpid)系統調用,  該系統調用使核心釋放為子進程保留的資源。

只有在1成立且2不成立的條件下,才會出現殭屍進程, 也就是說進程終止(1)後,其進程描述符尚未從進程表刪除之前,就是所謂的殭屍進程。殭屍進程可能穩定地存在於進程表中,直至系統重啟。

三. 如何避免殭屍進程

首先說明一下,僵死是每個進程(init除外)必須經曆的,此處要討論的是怎麼儘快處理掉殭屍進程。

1. 父進程調用 wait4() 或waipid() 系統調用,還有其他wait() 類庫函數,如wait3() 和 wait() 但在Linux中這些庫函數是靠 wait4() 和 watipid() 系統調用來實現的

由於父進程調用wait()會阻塞,故經典的做法是父進程註冊SIGCHLD訊號,在訊號處理函數中wait()

void ouch(int sig){    wait();  // 在訊號處理函數中wait()}main(){    signal(SIGCHLD, ouch);    // do what you wat}

2. 兩次fork , 利用孤兒進程(orphan process )

具體方法為首先用父進程fork()一個子進程,同時父進程阻塞等待。然後讓子進程立刻fork()一個孫子進程,並讓子進程則立刻退出。用孫子進程來處理事務。這時候由於子進程已經退出,孫子進程就變成了孤兒進程,被init領養。而子進程立刻退出後,父進程收到訊號並正確銷毀了子進程。

詳見APUE相關章節

3. 父進程忽略 SIGCHLD 訊號(這個使用有限制,貌似僅限於Linux 2.6和更高核心)

    signal(SIGCHLD, SIG_IGN); // 忽略SIGCHLD訊號    pid = fork(); // 這裡的子進程就不會殭屍了

man sigaction 中有這樣的描述:

POSIX.1-1990  disallowed setting the action for SIGCHLD to SIG_IGN.  POSIX.1-2001 allows this possibility,so that ignoring SIGCHLD can be used to prevent the creation of zombies (see wait(2)).  Nevertheless,  thehistorical  BSD  and  System V behaviors for ignoring SIGCHLD differ, so that the only completely portablemethod of ensuring that terminated children do not become zombies is to catch the SIGCHLD signal andperform a wait(2) or similar.

man 2 wait  中這樣的

POSIX.1-2001  specifies  that  if the disposition of SIGCHLD is set to SIG_IGN or the SA_NOCLDWAIT flag isset for SIGCHLD (see sigaction(2)), then children that terminate do not  become  zombies  and  a  call  towait() or waitpid() will block until all children have terminated, and then fail with errno set to ECHILD.(The original POSIX standard left the behavior of setting SIGCHLD to SIG_IGN unspecified.  Note that  eventhough  the  default  disposition  of  SIGCHLD  is "ignore", explicitly setting the disposition to SIG_IGNresults in different treatment of zombie process children.)  Linux 2.6  conforms  to  this  specification.However,  Linux  2.4  (and earlier) does not: if a wait() or waitpid() call is made while SIGCHLD is beingignored, the call behaves just as though SIGCHLD were not being ignored, that is, the  call  blocks  untilthe next child terminates and then returns the process ID and status of that child.

如若,父進程已經忽略了 SIGCHLD 訊號, 然後又調用了 wait4() 或 waitpid() 會怎樣呢?

kernle/signal.c 中函數do_notify_parent(struct task_struct *tsk, int sig) 有這樣的說明:

/** We are exiting and our parent doesn't care.  POSIX.1 defines special semantics for setting SIGCHLD to SIG_IGN* or setting the SA_NOCLDWAIT flag: we should be reaped automatically and not left for our parent's wait4 call.* Rather than having the parent do it as a magic kind of signal handler, we just set this to tell do_exit that* we can be cleaned up without becoming a zombie.  Note that we still call __wake_up_parent in this case,* because a blocked sys_wait4 might now return -ECHILD.** Whether we send SIGCHLD or not for SA_NOCLDWAIT is implementation-defined: we do (if you don't want* it, just use SIG_IGN instead).*/

那麼, 為什麼父進程signal(SIGCHLD, SIG_IGN);後子進程就不會殭屍了呢? 難道是子進程繼承了父進程忽略SIGCHLD屬性的原因。

我以為子進程在退出時執行exit() 函數會檢查SIGCHLD是否忽略。如果忽略,核心就會在do_exit() 函數中清理子進程的所有資源。

做了個小實驗,證明我錯了

int main(){    int i=0;    pid_t pid = 0;    signal(SIGCHLD, SIG_IGN); //父進程 ignore SIGCHLD    pid = fork();    if(pid > 0)    {        puts("I am parent");        for(i=0; i<60; i++)        {            printf("parent: %d\n",i);            sleep(1);        }    }    else if(pid == 0)    {        signal(SIGCHLD, SIG_DFL); //子進程恢複SIGCHLD預設行為        puts("I am child");        exit(0);    }    return 0;}

程式執行後,並沒有殭屍進程出現。 可見設定父進程signal(SIGCHLD, SIG_IGN);並沒有影響到子進程的行外,起碼在子進程exit()時沒有影響。

那麼,到底是什麼原因呢?

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.