• 僵尸进程 zombie


    On Unix and Unix-like computer operating systems, a zombie process or defunct process is a process that has completed execution (via the exit system call) but still has an entry in the process table: it is a process in the "Terminated state". This occurs for the child processes, where the entry is still needed to allow the parent process to read its child's exit status: once the exit status is read via the wait system call, the zombie's entry is removed from the process table and it is said to be "reaped". A child process always first becomes a zombie before being removed from the resource table. In most cases, under normal system operation zombies are immediately waited on by their parent and then reaped by the system – processes that stay zombies for a long time are generally an error and cause a resource leak, but the only resource they occupy is the process table entry – process ID.

    The term zombie process derives from the common definition of zombie — an undead person. In the term's metaphor, the child process has "died" but has not yet been "reaped". Also, unlike normal processes, the kill command has no effect on a zombie process.

    Zombie processes should not be confused with orphan processes: an orphan process is a process that is still executing, but whose parent has died. When the parent dies, the orphaned child process is adopted by init (process ID 1). When orphan processes die, they do not remain as zombie processes; instead, they are waited on by init. The result is that a process that is both a zombie and an orphan will be reaped automatically.

    Overview[edit]

    When a process ends via exit, all of the memory and resources associated with it are deallocated so they can be used by other processes. However, the process's entry in the process table remains. The parent can read the child's exit status by executing the wait system call, whereupon the zombie is removed. The wait call may be executed in sequential code, but it is commonly executed in a handler for the SIGCHLD signal, which the parent receives whenever a child has died.

    After the zombie is removed, its process identifier (PID) and entry in the process table can then be reused. However, if a parent fails to call wait, the zombie will be left in the process table, causing a resource leak. In some situations this may be desirable – the parent process wishes to continue holding this resource – for example if the parent creates another child process it ensures that it will not be allocated the same PID. On modern UNIX-like systems (that comply with SUSv3 specification in this respect), the following special case applies: if the parent explicitly ignores SIGCHLD by setting its handler to SIG_IGN (rather than simply ignoring the signal by default) or has the SA_NOCLDWAIT flag set, all child exit status information will be discarded and no zombie processes will be left.[1]

    Zombies can be identified in the output from the Unix ps command by the presence of a "Z" in the "STAT" column.[2] Zombies that exist for more than a short period of time typically indicate a bug in the parent program, or just an uncommon decision to not reap children (see example). If the parent program is no longer running, zombie processes typically indicate a bug in the operating system. As with other resource leaks, the presence of a few zombies is not worrisome in itself, but may indicate a problem that would grow serious under heavier loads. Since there is no memory allocated to zombie processes – the only system memory usage is for the process table entry itself – the primary concern with many zombies is not running out of memory, but rather running out of process table entries, concretely process ID numbers.

    To remove zombies from a system, the SIGCHLD signal can be sent to the parent manually, using the kill command. If the parent process still refuses to reap the zombie, and if it would be fine to terminate the parent process, the next step can be to remove the parent process. When a process loses its parent, init becomes its new parent. init periodically executes the wait system call to reap any zombies with init as parent.

    Example[edit]

    Synchronously waiting for the specific child processes in a (specific) order may leave zombies present longer than the above-mentioned "short period of time". It is not necessarily a program bug.

    #include <sys/wait.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main(void)
    {
        pid_t pids[10];
        int i;
    
        for (i = 9; i >= 0; --i) {
            pids[i] = fork();
            if (pids[i] == 0) {
                printf("Child%d
    ", i);
                sleep(i+1);
                _exit(0);
            }
        }
    
        for (i = 9; i >= 0; --i) {
            printf("parent%d
    ", i);
            waitpid(pids[i], NULL, 0);
        }
    
        return 0;
    }
    

    Output[edit]

    parent9
    Child3
    Child4
    Child2
    Child5
    Child1
    Child6
    Child0
    Child7
    Child8
    Child9 // there is a pause here
    parent8
    parent7
    parent6
    parent5
    parent4
    parent3
    parent2
    parent1
    parent0
    

    Explanation[edit]

    In the first loop, the original (parent) process forks 10 copies of itself. Each of these child processes (detected by the fact that fork() returned zero) prints a message, sleeps, and exits. All of the children are created at essentially the same time (since the parent is doing very little in the loop), so it's somewhat random when each of them gets scheduled for the first time - thus the scrambled order of their messages.

    During the loop, an array of child process IDs is built. There is a copy of the pids[] array in all 11 processes, but only in the parent is it complete - the copy in each child will be missing the lower-numbered child PIDs, and have zero for its own PID. (Not that this really matters, as only the parent process actually uses this array.)

    The second loop executes only in the parent process (because all of the children have exited before this point), and waits for each child to exit. It waits for the child that slept 10 seconds first; all the others have long since exited, so all of the messages (except the first) appear in quick succession. There is no possibility of random ordering here, since it's driven by a loop in a single process. Note that the first parent message actually appeared before any of the children messages - the parent was able to continue into the second loop before any of the child processes were able to start. This again is just the random behavior of the process scheduler - the "parent9" message could have appeared anywhere in the sequence prior to "parent8".

    Child0 through Child8 spend one or more seconds in this state, between the time they exited and the time the parent did a waitpid() on them. The parent was already waiting on Child9 before it exited, so that one process spent essentially no time as a zombie. [3]

    See also[edit]

    References[edit]

     [Zombie process - Wikipedia](https://en.wikipedia.org/wiki/Zombie_process)

    类UNIX系统中,僵尸进程是指完成执行(通过exit系统调用,或运行时发生致命错误或收到终止信号所致),但在操作系统的进程表中仍然存在其进程控制块,处于"终止状态"的进程。这发生于子进程需要保留表项以允许其父进程读取子进程的退出状态:一旦退出态通过wait系统调用读取,僵尸进程条目就从进程表中删除,称之为"回收"(reaped)。正常情况下,进程直接被其父进程wait并由系统回收。进程长时间保持僵尸状态一般是错误的并导致资源泄漏

    英文术语zombie process源自丧尸——不死之人,隐喻子进程已死但仍然没有被回收。与正常进程不同,kill命令对僵尸进程无效。孤儿进程不同于僵尸进程,其父进程已经死掉,但孤儿进程仍能正常执行,但并不会变为僵尸进程,因为被init(进程ID号为1)收养并wait其退出。

    子进程死后,系统会发送SIGCHLD信号给父进程,父进程对其默认处理是忽略。如果想响应这个消息,父进程通常在信号事件处理程序中,使用wait系统调用来响应子进程的终止。

    僵尸进程被回收后,其进程号与在进程表中的表项都可以被系统重用。但如果父进程没有调用wait,僵尸进程将保留进程表中的表项,导致了资源泄漏。某些情况下这反倒是期望的:父进程创建了另外一个子进程,并希望具有不同的进程号。如果父进程通过设置事件处理函数为SIG_IGN显式忽略SIGCHLD信号,而不是隐式默认忽略该信号,或者具有SA_NOCLDWAIT标志,所有子进程的退出状态信息将被抛弃并且直接被系统回收。

    UNIX命令ps列出的进程的状态("STAT")栏标示为 "Z"则为僵尸进程。[1]

    收割僵尸进程的方法是通过kill命令手工向其父进程发送SIGCHLD信号。如果其父进程仍然拒绝收割僵尸进程,则终止父进程,使得init进程收养僵尸进程。init进程周期执行wait系统调用收割其收养的所有僵尸进程。

    为避免产生僵尸进程,实际应用中一般采取的方式是:

    1. 将父进程中对SIGCHLD信号的处理函数设为SIG_IGN(忽略信号);
    2. fork两次并杀死一级子进程,令二级子进程成为孤儿进程而被init所“收养”、清理[2]

    制造僵尸进程

    cpp@a:~/myZombie$ cat zombie.c
    #include <stdio.h>
    #include <sys/wait.h>
    #include <stdlib.h>
    #include <unistd.h>

    int main(void)
    {
    pid_t pids[10];
    int i;

    for (i = 9; i >= 0; --i) {
    pids[i] = fork();
    if (pids[i] == 0) {
    printf("Child%d ", i);
    sleep(i+1);
    _exit(0);
    }
    }

    for (i = 9; i >= 0; --i) {
    printf("parent%d ", i);
    waitpid(pids[i], NULL, 0);
    }

    return 0;
    }
    cpp@a:~/myZombie$
    cpp@a:~/myZombie$ gcc zombie.c
    cpp@a:~/myZombie$ ./a.out


    root@a:/home/cpp#
    root@a:/home/cpp# ps -ef | grep fun
    cpp 9141 9061 0 14:41 tty2 00:00:00 [fcitx] <defunct>
    cpp 10931 10922 0 15:01 pts/0 00:00:00 [a.out] <defunct>
    cpp 10932 10922 0 15:01 pts/0 00:00:00 [a.out] <defunct>
    root 10934 10618 0 15:01 pts/1 00:00:00 grep --color=auto fun
    root@a:/home/cpp# top
    top - 15:01:18 up 2 days, 16 min, 3 users, load average: 0.13, 0.05, 0.09
    Tasks: 205 total, 1 running, 199 sleeping, 0 stopped, 5 zombie
    %Cpu(s): 0.0 us, 6.2 sy, 0.0 ni, 93.8 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
    MiB Mem : 9966.4 total, 7414.6 free, 1023.2 used, 1528.5 buff/cache
    MiB Swap: 2048.0 total, 2048.0 free, 0.0 used. 8657.9 avail Mem
    Unknown command - try 'h' for help
    PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
    1 root 20 0 102996 12260 7844 S 0.0 0.1 0:02.11 systemd
    2 root 20 0 0 0 0 S 0.0 0.0 0:00.01 kthreadd
    3 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 rcu_gp
    4 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 rcu_par_gp
    6 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/0:0H-kblockd

    如何查看并杀死僵尸进程 - 知乎 https://zhuanlan.zhihu.com/p/92381918

    如何查看并杀死僵尸进程

    In UNIX System terminology, a process that has terminated,but whose parent has not yet waited for it, is called a zombie.

    在UNIX 系统中,一个进程结束了,但是他的父进程没有等待(调用wait / waitpid)他, 那么他将变成一个僵尸进程. 在fork()/execve()过程中,假设子进程结束时父进程仍存在,而父进程fork()之前既没安装SIGCHLD信号处理函数调用 waitpid()等待子进程结束,又没有显式忽略该信号,则子进程成为僵尸进程。

    如何查看linux系统上的僵尸进程,如何统计有多少僵尸进程?

    #ps -ef | grep defunct

    或者查找状态为Z的进程,Z就是代表zombie process,僵尸进程的意思。

    另外使用top命令查看时有一栏为S,如果状态为Z说明它就是僵尸进程。

    Tasks: 95 total, 1 running, 94 sleeping, 0 stopped, 0 zombie

    top命令中也统计了僵尸进程。或者使用下面的命令:

    ps -ef | grep defunct | grep -v grep | wc -l

    如何杀死僵尸进程呢?

    一般僵尸进程很难直接kill掉,不过您可以kill僵尸爸爸。父进程死后,僵尸进程成为”孤儿进程”,过继给1号进程init,init始终会负责清理僵尸进程.它产生的所有僵尸进程也跟着消失。

    ps -e -o ppid,stat | grep Z | cut -d” ” -f2 | xargs kill -9

    kill -HUP `ps -A -ostat,ppid | grep -e ’^[Zz]‘ | awk ’{print $2}’`

    当然您可以自己编写更好的shell脚本,欢迎与大家分享。

    另外子进程死后,会发送SIGCHLD信号给父进程,父进程收到此信号后,执行waitpid()函数为子进程收尸。就是基于这样的原理:就算父进程没有调用wait,内核也会向它发送SIGCHLD消息,而此时,尽管对它的默认处理是忽略,如果想响应这个消息,可以设置一个处理函数。

    如何避免僵尸进程呢?

    处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结 束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在Linux下 可以简单地将 SIGCHLD信号的操作设为SIG_IGN。 signal(SIGCHLD,SIG_IGN); 这样,内核在子进程结束时不会产生僵尸进程。这一点与BSD4不同,BSD4下必须显式等待子进程结束才能释放僵尸进程

    或者

    用两次fork(),而且使紧跟的子进程直接退出,是的孙子进程成为孤儿进程,从而init进程将负责清除这个孤儿进程。

    # top
    top - 14:49:04 up 2 days, 4 min, 2 users, load average: 0.10, 0.30, 0.24
    Tasks: 192 total, 1 running, 190 sleeping, 0 stopped, 1 zombie
    %Cpu(s): 0.3 us, 0.7 sy, 0.0 ni, 99.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
    MiB Mem : 9966.4 total, 7415.2 free, 1052.8 used, 1498.3 buff/cache
    MiB Swap: 2048.0 total, 2048.0 free, 0.0 used. 8628.4 avail Mem

    PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
    803 redis 20 0 55768 4500 3212 S 0.3 0.0 3:29.94 redis-server
    9667 shawn 20 0 747872 55180 40748 S 0.3 0.5 0:01.98 gnome-system-mo
    9683 root 20 0 0 0 0 I 0.3 0.0 0:00.12 kworker/0:3-events
    1 root 20 0 102996 12260 7844 S 0.0 0.1 0:02.11 systemd
    2 root 20 0 0 0 0 S 0.0 0.0 0:00.01 kthreadd
    3 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 rcu_gp
    4 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 rcu_par_gp
    6 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/0:0H-kblockd
    9 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 mm_percpu_wq
    10 root 20 0 0 0 0 S 0.0 0.0 0:02.57 ksoftirqd/0
    11 root 20 0 0 0 0 I 0.0 0.0 1:06.26 rcu_sched
    12 root rt 0 0 0 0 S 0.0 0.0 0:01.40 migration/0

  • 相关阅读:
    MQTT协议的简单介绍和服务器的安装
    Scrollview 嵌套 RecyclerView 及在Android 5.1版本滑动时 惯性消失问题
    gradle 命令
    git 命令学习
    PHP、JAVA、C#、Object-C 通用的DES加密
    JS中树形对象与数组之间的相互转换
    Javascript中的类型转换
    Javascript中的基本数据类型,如何判断数据类型,作用域链的理解
    前端面试题集锦(二)之CSS部分
    前端面试题集锦(一)之HTML部分
  • 原文地址:https://www.cnblogs.com/rsapaper/p/6675064.html
Copyright © 2020-2023  润新知