3

I am spent whole half a day, but still couldn't figure, why is the dash being launched with execl call just becomes a zombie.

Below is a minimal test case — I'm just fork a child, make a duplicate of std [in,out,err] descriptors, and launch the sh.

#include <cstdio>
#include <fcntl.h>
#include <cstring>
#include <stdlib.h>
#include <cerrno>
#include <unistd.h>

int main() {
    int pipefd[2];
    enum {
        STDOUT_TERM = 0,
        STDIN_TERM  = 1
    };
    if (pipe(pipefd) == -1) { //make a pipe
        perror("pipe");
        return 0;
    }
    pid_t pid = fork();
    if (pid == 0)
    {// Child
        dup2(pipefd[STDIN_TERM], STDIN_FILENO);
        dup2(pipefd[STDOUT_TERM], STDOUT_FILENO);
        dup2(pipefd[STDOUT_TERM], STDERR_FILENO);
        execl("/bin/sh","sh", (char*)NULL);
        // Nothing below this line should be executed by child process. If so, print err
        perror("For creating a shell process");
        exit(1);
    }
    __asm("int3");
    puts("Child launched");
}

When I launch it in a debugger, and at the line with breakpoint (above the puts() call) look at the pid variable, and then look at the according process with ps, I every time getting something like

2794 pts/10   00:00:00 sh <defunct>

I.e. its a zombie

4
  • What exactly are you trying to do with the pipe? First of all, you seem to be creating 2 independant pipes, one in the parent and one in the child (because you call pipe after fork) then don't use the pipe at all in the parent. In the child, you dup2 the pipe to stdio, with the apparent net effect that you loop the child's stdout back to its own stdin! Moving on, I have no clue what __asm("int3") is supposed to do. This is architecture-specific voodoo? Can you do without that?
    – Celada
    Commented Apr 15, 2015 at 15:53
  • Next, I don't know exactly why the child shell that you execl is dying (might be related to the bizarre stdio loop) but you aren't waiting for it in the parent, so that would probably be why the child becomes a zombie. Finally, the parent appears to exit right after forking the child and creating an unused pipe (main finishes) so I would have thought the child should be inherited by init so I don't know why it's becoming a zombie.
    – Celada
    Commented Apr 15, 2015 at 15:56
  • 3
    P.S.: in typical fork-then-exec code, you should always call _exit in the child after the exec fails, not exit. This is because calling exit may execute cleanup code like atexit handlers that are only meant to be executed when the parent exits.
    – Celada
    Commented Apr 15, 2015 at 15:57
  • @Celada ah, you were right, it's a bug in the test case that the pipe called after the fork. Anyway, I just moved it higher, and the problem with sh remains. About what I am trying to do: I dup2ping file descriptors in order to be able to write to the launched sh, and get it's output. I.e. I want an application to be some kind of proxy to sh (because the app communicates via interfaces/protocols which isn't known to a system).
    – Hi-Angel
    Commented Apr 15, 2015 at 16:28

2 Answers 2

7

You're leaving a zombie, trivially, because you didn't wait on your child process.

Your shell is immediately exiting because you've set up its STDIN in a nonsensical way. pipe returns a one-way communications channel. You write to pipefd[1] and read it back from pipefd[0]. You did a buch of dup2 calls which lead the shell to attempt to read (STDIN) from the write end of the pipe.

Once you swap the numbers in your enum, you get the shell sitting forever on read. That's probably not what you want either, but it's about all you can expect when you've got a shell piped to itself.

Presuming you're attempting to use the shell from your parent process, you need to call pipe twice (and both in the parent): one of the pipes you write to (and the shell reads from, on stdin) and the other the shell writes to (stdout/stderr) and you read from. Or if you want, use socketpair instead.

1
  • Thank you very much, I indeed missed that I should have been call it twice.
    – Hi-Angel
    Commented Apr 15, 2015 at 16:31
2

You need to catch the SIGCHLD signal, and "reap" the zombie process with a wait() system call. Nearly minimal addition of code to your program to add a signal handler function, and set it as the SIGCHLD handler looks like this:

#include <cstdio>
#include <fcntl.h>
#include <cstring>
#include <stdlib.h>
#include <cerrno>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

void sighandler(int signal_number);

void sighandler(int signo) {
    if (signo == SIGCHLD) {
            int status;
            (void)wait(&status);
    }
}


int main() {
    int pipefd[2];
    enum {
        STDOUT_TERM = 0,
        STDIN_TERM  = 1
    };
    signal(SIGCHLD, sighandler);
    pid_t pid = fork();

You should almost certainly check the return status of the signal() system call, and look into dealing with the child's exit status (the value of status in the signal handler code.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .