What's the difference between various $SIG{CHLD} values?

There are two ways to avoid creating zombie processes:

  1. explicitly set $SIG{CHLD}='IGNORE'
  2. reap the dead children explictly with the wait or waitpid calls (this could be done inside a SIGCHLD handler, but it need not be)

Setting $SIG{CHLD}='IGNORE' traps the SIGCHLD at the operating system level, cleaning up the child process without even signalling your Perl program.

Any other setting, including 'DEFAULT', undef, "", sub {}, 'some_function_name_that_doesnt_even_exist' will cause the signal to be delivered to Perl, and the child will not be reaped automatically.

By reaping the process yourself with wait and waitpid, you can get additional information like the exit status of the child process, and (more-or-less) the order in which the child processes finished. If $SIG{CHLD} is set to 'IGNORE', wait and waitpid always return -1 and don't set $?.

The SIGCHLD, if any, is always delivered to the process that spawned the child process, so I don't think you are correct to say that a SIGCHLD from a grandchild process (from a system call in a child process) is caught in the parent process. Probably what is going on is that your child process inherits the signal handler from its parent process.

system and backticks will (on most systems) generate a SIGCHLD on completion and will set the $? value with the command's exit status. But Perl will reap these subprocesses itself, and you won't be able to capture the process id of a system or backticks call with wait or waitpid.


See %SIG.

$SIG{CHLD} = 'IGNORE'; causes your process to ignore SIGCHLD signals.

$SIG{CHLD} = 'DEFAULT'; causes your process to treat SIGCHLD signals as it would had you not messed with $SIG{CHLD} or equivalent. According to kill(1), a process on my system ignores SIGCHLD by default

$SIG{CHLD} = ''; and $SIG{CHLD} = undef; are not valid values.

As for reaping, children of a parent whose SIGCHLD handler is explicitely set to IGNORE will be reaped automatically by the system as soon as it exits.

$ perl -e'
   my $pid = fork;
   if (!$pid) { sleep(1); exit; }
   sleep(2);
   system "ps -o pid,stat,command $pid";
'
  PID STAT COMMAND
14667 ZN+  [perl] <defunct>

$ perl -e'
   $SIG{CHLD}="IGNORE";
   my $pid = fork;
   if (!$pid) { sleep(1); exit; }
   sleep(2);
   system "ps -o pid,stat,command $pid";
'
  PID STAT COMMAND

$

There are two different meaning for "ignore", and they are occurring at two different points of evaluation.

The first use concerns whether to deliver the signal at all. When a child processes exits, it usually is held by the operating system and a CHLD signal is sent to its parent. When setting $SIG{CHLD} to 'IGNORE', it tells the system to not generate a signal at all and just let the child process exit. The parent has no opportunity to get information on the child process, and no zombies result.

But if $SIG{CHLD} is anything other than 'IGNORE', it means to deliver the signal to the parent process, at which point there is either a custom signal handler or a default signal handler, and the default handler will vary on different systems or even versions of the same operating system. The book and signal(7) man page talk about what happens by default if a signal is delivered to the parent process (i.e. handler is not set to 'IGNORE'). So the second use of ignore is related to which action to take for a delivered signal. For example SIGINT will cause your process to be terminated, and SIGSTOP will cause your process to "pause", although custom signal handlers change these default actions.

For the SIGCHLD signal, the default is to "ignore" it, but in this usage it just means the (parent) process just continues executing normally while the child process waits (i.e. no termination or aborting of parent process). You can then wait() on it later to get it's information and avoid a zombie.