views:

127

answers:

3

I'm creating background processes in C using fork().

When I created one of these processes, I add its pid to an array so I can keep track of background processes.

    pid = fork();

    if(pid == -1) 
    {
        printf("error: fork()\n");
    }
    else if(pid == 0) 
    {
        execvp(*args, args);
        exit(0);
    }
    else  
    {
        // add process to tracking array
        addBGroundProcess(pid, args[0]);
    }

I have a handler for reaping zombies

void childHandler(int signum) { pid_t pid; int status;

/* loop as long as there are children to process */ 
while (1) { 

   /* get zombie pids */ 
   pid = waitpid(-1, &status, WNOHANG); 

   if (pid == -1)
   { 
       if (errno == EINTR)
       { 
           continue; 
       } 

       break; 
   } 
   else if (pid == 0)
   { 
       break; 
   } 

   /* Remove this child from tracking array */ 
   if (pid != mainPid)
        cleanUpChild(pid);
}    

}

When I create a background process, the handler is executing and attempting to clean up the child before I can even make the call to addBGroundProcess.

I'm using commands like emacs& which should not be exiting immediately.

What am I missing?

Thanks.

A: 

Your code just catches the exit of the child process it fork'ed, which is not to say that another process wasn't fork'ed by that child first. I'm guessing that emacs in your case is doing another fork() on itself for some reason, and then allowing the initial process to exit (that's a trick daemons will do).

The setsid() function might also be worth looking at, although without writing up some code myself to check it I'm not sure if that's relevant here.

Michael Krebs
Michael Krebs
+1  A: 

You're right, there is a race condition there. I suggest that you block the delivery of SIGCHLD using the sigprocmask function. When you have added the new PID to your data structure, unblock the signal again. When a signal is blocked, if that signal is received, the kernel remembers that it needs to deliver that signal, and when the signal is unblocked, it's delivered.

Here's what I mean, specifically:

sigset_t mask, prevmask;

//Initialize mask with just the SIGCHLD signal
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);

sigprocmask(SIG_BLOCK, &mask, &prevmask); /*block SIGCHLD, get previous mask*/
pid = fork();

if(pid == -1) 
{
    printf("error: fork()\n");
}
else if(pid == 0) 
{
    execvp(*args, args);
    exit(0);
}
else  
{
    // add process to tracking array
    addBGroundProcess(pid, args[0]);

    // Unblock SIGCHLD again
    sigprocmask(SIG_SETMASK, &prevmask, NULL);
}

Also, I think there's a possibility that execvp could be failing. (It's good to handle this in general, even if it's not happening in this case.) It depends exactly how it's implemented, but I don't think that you're allowed to put a & on the end of a command to get it to run in the background. Running emacs by itself is probably what you want in this case anyway, and putting & on the end of a command line is a feature provided by the shell.

Edit: I saw your comments about how you don't want emacs to run in the current terminal session. How do you want it to run, exactly - in a separate X11 window, perhaps? If so, there are other ways of achieving that.

A fairly easy way of handling execvp's failure is to do this:

    execvp(*args, args);
    perror("execvp failed");
    _exit(127);
Doug
A: 

You should not be using the shell with & to run background processes. If you do that, they come out as grandchildren which you cannot track and wait on. Instead you need to either mimic what the shell does to run background processes in your own code, or it would probably work just as well to close the terminal (or rather stdin/out/err) and open /dev/null in its place in the child processes so they don't try to write to the terminal or take control of it.

R..
If you don't want the command to use the terminal, why don't you want to close the terminal? You would be closing it in the **child** process, not the parent.
R..
Just miscommunicating... I do want to close th terminal to the child process, so that only the foreground process (my shell) can use it.