voyvoda
voyvoda

Reputation: 47

Killing zombie processes in Linux

I have a problem with zombie-processes. When I close the connection from the client side, zombie childs don't die. If I close the connection from the server side, everything is OK. There is no zombie child. I'm using the code below

Any Help ??

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

Upvotes: 1

Views: 3608

Answers (2)

Luis Colorado
Luis Colorado

Reputation: 12668

A zombie process consumes only the entry in the process table. The kernel maintains it to allow for the parent process' wait(2) system call (and family) to be aware that there's actually a process to be waited for and don't fail about calling wait() without having subprocesses walking around. Those walking dead processes are there to ensure kernel data consistency and, as such, you cannot kill them (even as root user) The only way to ensure that a living parent doesn't have this bunch of zombies around is to do one wait(2) call for each fork() it has done before (which you don't do at all). As in your code the thread is going to die just after closing the file descriptor, you have a chance to do a waitpid(pid_of_child, ...); there, so you'll wait for the proper child. See waitpid(2) for more info about this system call. This approach will have a non-visible drawback (your thread will last until the child dies). The reason this works normally with processes (the non-need to do wait() in the parent process) is that you are not dying the parent (the parent lives after the thread dies) and so, the fork()/wait() relationship maintains. When a parent dies, the kernel makes init (process with id == 1) the parent of your process, and init(8) is always making wait(2)s for the orphaned children in the system.

By just adding the following code after

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

or, as you are not going to check how did the child terminate

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

This has another drawback, is that you are going to wait for the child to terminate and will not get into the accept(2) system call before your child terminates, which can be not what you want. If you want to avoid creating child zombie processes, there's another alternative (but it has some other drawbacks) is to ignore the SIGCHLD signal in the whole process, which makes the kernel not create those zombies (legacy way to do, there are other ways to avoid zombie children) or you can have a new thread just making the needed wait()s and dispatch the returned values from the children to the proper place, once they die.

Upvotes: 2

ephemient
ephemient

Reputation: 204698

Do you really mean "zombie process" (Z state in ps)? Those are already dead, but they haven't been reaped by their parent yet.

  • The address arguments to recvfrom/sendto are useless on SOCK_STREAM sockets, and actually using values other than NULL/0 can fail on some implementations.
  • TCP isn't message-based, so checking pData[0] is faulty. A client sending "^A\r\n^B\r\n" could legitimately be received as "^" followed by "A\r\n^B\r\n" or as "^A\r\n^" followed by "B\r\n" or any other split.
  • It's possible that you get a short write while trying to send "OK\r\n".
  • If recvfrom returns <0, as it does in an error state like trying to read from a shutdown socket, you loop forever in while(1). This is not a zombie - it's a running process, although one burning CPU for no use.
  • In the parent process, you don't handle SIGCHLD nor call wait/waitpid/etc. This means that when children exit, they aren't reaped, and this will result in actual zombie processes.

Upvotes: 0

Related Questions