10

It turns out that we can prevent appearing of a zombie process (i.e. the one whose parent doesn't wait() for it to _exit()) by specifying SIGCHLD signal to be ignored with sigaction() by its parent. However, it seems like SIGCHLD is ignored by default anyway. How come does this work?

int main (void) {
    struct sigaction sa;
    sa.sa_handler = SIG_IGN; //handle signal by ignoring
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    if (sigaction(SIGCHLD, &sa, 0) == -1) {
        perror(0);
        exit(1);
    }
    int pid = fork();
    if (pid == 0) { //child process
        _exit(0);
    }
    do_something(); //parent process
    return 0;
}
igor
  • 131
  • 1
  • 7

2 Answers2

15

The default behavior of SIGCHLD is to discard the signal, but the child process is kept as a zombie until the parent calls wait() (or a variant) to get its termination status.

But if you explicitly call sigaction() with the disposition SIG_IGN, that causes it not to turn the child into a zombie -- when the child exits it is reaped immediately. See https://stackoverflow.com/a/7171836/1491895

The POSIX way to get this behavior is by calling sigaction with handler = SIG_DFL and flags containing SA_NOCLDWAIT. This is in Linux since 2.6.

Community
  • 1
  • 1
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • Does this mean that discarding signal and ignoring signal are two different behaviors? – igor Nov 15 '16 at 03:31
  • What's special is ignoring it explicitly by calling `sigaction()`. It doesn't change how the signal is treated in your process, but it changes what happens to the process that exits. – Barmar Nov 15 '16 at 16:50
  • 2
    @igor Yes, discarding a signal and ignoring it are different things. Discarding means the signal is basically just thrown away, while ignoring means it's actually considered delivered, but there is no handler or any other special action performed. – twalberg Nov 15 '16 at 18:20
  • 1
    @twalberg But except in this special case, there's no behavioral difference between them. – Barmar Nov 15 '16 at 19:08
  • 1
    @Barmar That may be true in this particular case, but it's not necessarily true across all the different signals. Thus, it can be important to understand the difference between throwing away a signal and delivering it but not doing anything in response... – twalberg Nov 15 '16 at 19:23
  • @twalberg My claim is that this is the only case where there's a difference. Are there others I didn't think of? – Barmar Nov 15 '16 at 19:27
  • Or are you saying that this is the only case now, but we can't predict the future? – Barmar Nov 15 '16 at 19:27
  • @Barmar `kill -l` on my Linux system lists 64 different signals. Not having fully memorized the semantics of all of those, I can't say that for sure - hence the "not necessarily". There might be others where the difference is significant, but I can't state definitively one way or the other without more research. – twalberg Nov 15 '16 at 19:34
  • @twalberg This is the only one I know of that has this special behavior. I'll stick by my assertion unless someone can provide a counterexample. In any case, it's not germane to the answer. – Barmar Nov 15 '16 at 19:36
  • @Barmar If I don't care about my child process' exit status then instead of using `wait` can I simply use `signal(SIGCHLD, SIG_IGN);` in order to prevent zombie processes? – patoglu Apr 24 '20 at 00:31
  • 1
    The portable method is what I described in my last paragraph. – Barmar Apr 24 '20 at 05:02
2

The confusion is that you seem to be asuming that "SIGCHLD is ignored by default" means that the default disposition of the signal is SIG_IGN.

That's not the case. The default disposition is SIG_DFL. When you set it to SIG_IGN you're actually changing it. And after you've set it as SIG_IGN, you can revert it back to SIG_DFL, getting the original behavior again.

Daniel Munoz
  • 1,865
  • 1
  • 14
  • 9