【问题标题】:Killing zombie processes in Linux在 Linux 中杀死僵尸进程
【发布时间】:2017-03-19 20:46:16
【问题描述】:

我遇到了僵尸进程的问题。当我从客户端关闭连接时,僵尸孩子不会死。如果我从服务器端关闭连接,一切正常。没有僵尸孩子。我正在使用下面的代码

任何帮助??

#define SERV_PORT   1051
#define LISTENQ     1024


void sig_chld(int signo)
{
    pid_t   pid;
    int     stat;

    while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
        printf("child %d terminated\n", pid);
    return;
}


void *pThread_TCP(void *ptr)
{
    int                 listenfd, connfd;
    pid_t               childpid;
    socklen_t           clilen;
    struct sockaddr_in  cliaddr, servaddr;
    void                sig_chld(int);
    unsigned char       pData[255];
    unsigned short      n;

    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family      = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port        = htons(SERV_PORT);

    struct ifreq ifr;
    memset(&ifr, 0, sizeof(struct ifreq));
    snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "usb0");
    ioctl(listenfd, SIOCGIFINDEX, &ifr);
    setsockopt(listenfd, SOL_SOCKET, SO_BINDTODEVICE,  (void*)&ifr, sizeof(ifr));

    bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));

    listen(listenfd, LISTENQ);

    signal(SIGCHLD, sig_chld);  /* must call waitpid() */

    for ( ; ; ) {
        clilen = sizeof(cliaddr);
        if ( (connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen)) < 0) 
        {
            if (errno == EINTR)
                continue;       /* back to for() */
        }

        if ( (childpid = fork()) == 0)
        {   /* child process */
            while(1)
            {
                n = recvfrom(connfd,pData,100,0,(struct sockaddr *)&cliaddr,&clilen);
                if(n>0)
                {
                    if(pData[0] == '^') break;

                    sendto(connfd,"OK\r\n",4,0,(struct sockaddr *)&cliaddr,sizeof(cliaddr));

                }
            }
            close(listenfd);    /* close listening socket */
            exit(0);
        }
        close(connfd);          /* parent closes connected socket */
    }
}

【问题讨论】:

    标签: linux process zombie-process


    【解决方案1】:

    僵尸进程只消耗进程表中的条目。内核对其进行维护以允许父进程的wait(2) 系统调用(和家庭)意识到实际上有一个进程需要等待,并且在没有子进程四处走动的情况下调用wait() 不会失败。那些行尸走肉的进程是为了确保内核数据的一致性,因此,你不能杀死它们(即使作为 root 用户)确保活着的父母没有这群僵尸的唯一方法是做一个 @987654323 @调用它之前做过的每个fork()(你根本不做)。在您的代码中,线程将在关闭文件描述符后立即死亡,您有机会在那里执行waitpid(pid_of_child, ...);,因此您将等待合适的孩子。有关此系统调用的更多信息,请参阅waitpid(2)。这种方法会有一个不可见的缺点(您的线程将持续到孩子死去)。这在进程中正常工作的原因(不需要在父进程中执行wait())是因为您不会使父进程死亡(父进程在线程死亡后仍然存在),因此fork()/wait() 关系得以维持。当父进程死亡时,内核将 init(带有id == 1 的进程)设为您进程的父进程,而init(8)总是为系统中的孤儿创建wait(2)s。 p>

    只需在后面添加以下代码

        ...
        close(connfd);          /* parent closes connected socket */
        int retcode;  /* return code of child process */
        waitpid(childpid, &retcode, 0);
    } /* for loop */
    

    或者,因为您不会检查孩子是如何终止的

        ...
        close(connfd);          /* parent closes connected socket */
        waitpid(childpid, 0, 0);
    } /* for loop */
    

    这还有一个缺点,就是您将等待子进程终止,并且在您的子进程终止之前不会进入accept(2) 系统调用,这可能不是您想要的。如果你想避免创建子僵尸进程,还有另一种选择(但它有一些其他缺点)是在整个进程中忽略SIGCHLD 信号,这使得内核不会创建那些僵尸进程(传统的做法,有其他避免僵尸孩子的方法)或者你可以有一个新线程来创建所需的wait()s,并在孩子死后将返回的值分派到适当的地方。

    【讨论】:

    • @voyvoda,已添加!这是家庭作业吗?如果是这样,我认为现在包含注释就足够了,您必须弄清楚,并阅读手册页以获取有关 waitpid(2) 系统调用的第二个和第三个参数的信息。
    • 如果你使用线程来管理传入的连接套接字,你为什么要创建一个完整的进程来处理连接?为什么不创建一个新线程来处理连接并共享主数据内存?你有没有想过用这种方法来解决问题。线程和进程意味着非常不同的东西,因此在项目中混合它们几乎总是糟糕的设计。使用线程或进程,但不能同时使用...如果您不打算使用 fork()exec() 来生成不同的程序。
    【解决方案2】:

    您真的是指“僵尸进程”(ps 中的Z 状态)吗?那些已经死了,但还没有被父母收割。

    • recvfrom/sendto 的地址参数在 SOCK_STREAM 套接字上是无用的,实际上使用 NULL/0 以外的值在某些实现上可能会失败。
    • TCP 不是基于消息的,因此检查pData[0] 是错误的。发送"^A\r\n^B\r\n" 的客户端可以合法地以"^" 后跟"A\r\n^B\r\n""^A\r\n^" 后跟"B\r\n" 或任何其他拆分形式接收。
    • 您在尝试发送 "OK\r\n" 时可能会收到简短的写信。
    • 如果recvfrom 返回&lt;0,就像它在尝试从关闭的套接字读取一样的错误状态下,您将永远循环在while(1) 中。这不是僵尸——它是一个正在运行的进程,虽然一个烧掉的 CPU 没用。
    • 在父进程中,不处理SIGCHLD,也不调用wait/waitpid/等。这意味着当孩子退出时,他们不会被收割,这将导致实际的僵尸进程。

    【讨论】:

    • 那你有什么建议?
    • 传递 send/recv 而不是 sendto/recvfrom 或传递 NULL 作为 sockaddr/addrlen 参数。一次缓冲从客户端和流程线接收到的所有数据。缓冲要发送到客户端的数据,并尽可能尝试再次发送尚未写入的任何内容。如果返回是否定的,检查errno;重试EINTR,但您应该在大多数其他情况下终止(可能是_exit)。最简单的方法是将SIGCHLD 处理程序设置为SIG_IGN
    • 感谢您一一解释。你能分享一个与我的问题相关的例子吗?
    猜你喜欢
    • 2013-06-01
    • 1970-01-01
    • 2011-09-14
    • 2014-06-19
    • 1970-01-01
    • 1970-01-01
    • 2015-04-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多