在 Linux 中杀死僵尸进程

Killing zombie processes in Linux

我对 zombie-processes 有疑问。当我从客户端关闭连接时,僵尸 child 并没有死。如果我从服务器端关闭连接,一切正常。没有僵尸child。我正在使用下面的代码

任何帮助??

#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 */
    }
}

您的意思是 "zombie process"(Z 状态 ps)?那些已经死了,但他们还没有被他们的 parent 收割。

  • recvfrom/sendto 的地址参数在 SOCK_STREAM 套接字上没有用,实际上使用 NULL/0 以外的值在某些实现上可能会失败。
  • TCP 不是 message-based,因此检查 pData[0] 是错误的。发送 "^A\r\n^B\r\n" 的客户端可以合法地接收为 "^" 后跟 "A\r\n^B\r\n""^A\r\n^" 后跟 "B\r\n" 或任何其他拆分。
  • 您在尝试发送 "OK\r\n" 时可能会收到短消息。
  • 如果 recvfrom returns <0,因为它在尝试从关闭的套接字中读取等错误状态下所做的,您将在 while(1) 中永远循环。这不是僵尸 - 它是一个 运行 进程,虽然一个燃烧 CPU 没有用。
  • 在parent进程中,你不处理SIGCHLD也不调用wait/waitpid/等。这意味着当 children 退出时,它们不会被回收,这将导致实际的僵尸进程。

僵尸进程只消耗进程table中的条目。内核维护它以允许 parent 进程的 wait(2) 系统调用(和系列)知道实际上有一个进程要等待并且不要失败调用 wait()没有子流程四处走动。那些行尸走肉的进程是为了确保内核数据的一致性,因此,你不能杀死它们(即使是 root 用户)确保活着的 parent 周围没有这群僵尸的唯一方法是为它之前完成的每个 fork() 做一个 wait(2) 调用(你根本不做)。在您的代码中,线程将在关闭文件描述符后立即终止,您有机会在那里执行 waitpid(pid_of_child, ...);,因此您将等待正确的 child。有关此系统调用的更多信息,请参阅 waitpid(2)。这种方法将有一个 non-visible 缺点(您的线程将持续到 child 结束)。这与进程一起正常工作的原因(non-need 在 parent 进程中执行 wait())是因为你不会死于 parent(parent 活着线程死后)等等, fork()/wait() 关系保持不变。当 parent 死掉时,内核使 init (process with id == 1) 成为你进程的 parent,而 init(8) 总是 为系统中的孤儿 children 制作 wait(2)s。

只需在

之后添加以下代码
    ...
    close(connfd);          /* parent closes connected socket */
    int retcode;  /* return code of child process */
    waitpid(childpid, &retcode, 0);
} /* for loop */

或者,因为您不打算检查 child 是如何终止的

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

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