2017-03-19 141 views
0

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

任何帮助?

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

回答

1

僵尸进程只消耗进程表中的条目。内核维护它以允许父进程'wait(2)系统调用(和系列)知道实际上有一个要等待的进程,并且不要在调用wait()时没有子进程四处走动。那些正在行走的死进程是为了确保内核数据的一致性,因此,你不能杀死它们(即使是root用户)确保活着的父母没有这一群僵尸的唯一方法是做一个wait(2)调用每个fork()它已经做过(你根本没有做过)。在你的代码中,线程在关闭文件描述符后就会死掉,你有机会在那里做waitpid(pid_of_child, ...);,所以你会等待适当的孩子。有关此系统调用的更多信息,请参阅waitpid(2)。这种方法将有一个不可见的缺点(你的线程将持续到孩子死亡)。这与进程正常工作的原因(在父进程中不需要执行wait())是因为您不会死于父进程(父进程在线程死后生存),因此关系保持不变。当父母死亡时,内核会将init(与id == 1一起处理)作为您的进程的父级,并且init(8)总是为系统中的孤儿提供wait(2)

通过刚刚加入以下代码后

... 
    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 */ 

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

+0

你能提供任何代码示例吗? – voyvoda

+0

@voyvoda,补充!这是家庭任务吗?如果是这样,我认为现在足够包含评论,你必须弄清楚它,并阅读手册页以获取有关'waitpid(2)'系统调用的第二个和第三个参数的信息。 –

+0

如果您使用线程来管理传入连接套接字,为什么要创建一个处理连接的整个流程?为什么不创建一个新线程来处理连接和共享主数据存储器?你有没有想过以这种方式解决问题。线程和进程意味着非常不同的东西,所以把它们混合到一个项目中几乎总是糟糕的设计。使用线程或进程,但不能同时使用......更多的是,如果你不想使用'exec()'来跟踪'fork()'来产生一个不同的程序。 –

0

你真的意味着 “僵尸程序”(在psZ状态)?那些已经死了,但他们还没有被他们的父母收割。

  • 地址参数recvfrom/sendtoSOCK_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返回<0,就像它试图从关闭套接字中读取错误状态一样,您将永远循环到while(1)。这不是一个僵尸 - 它是一个正在运行的过程,尽管一个燃烧的CPU没有用处。
  • 在父进程中,您不处理SIGCHLD或拨打wait/waitpid/etc。这意味着当孩子离开时,他们没有被收割,这将导致实际的僵尸进程。
+0

那么你的建议是什么? – voyvoda

+0

通过'send' /'recv'代替'sendto' /'recvfrom'或传递'NULL'作为'sockaddr' /'addrlen'参数。一次缓冲从客户端和进程行收到的所有数据。缓冲数据发送到客户端,并尝试再次发送任何尚未写入的信息。如果返回是负数,请检查“errno”;在'EINTR'上重试,但是在大多数其他情况下你应该终止(可能是'_exit')。最简单的方法是将'SIGCHLD'处理程序设置为'SIG_IGN'。 – ephemient

+0

谢谢你一一解释。你能不能分享一个与我的问题有关的例子? – voyvoda