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

Trueblood picture Trueblood · Dec 5, 2011 · Viewed 12k times · Source

What is the difference between these settings?

$SIG{CHLD} = 'IGNORE'  
$SIG{CHLD} = 'DEFAULT'  
$SIG{CHLD} = ''  
$SIG{CHLD} = undef

According to "Advanced Programming in the UNIX Environment, 2nd edition", figure 10.1 the default value of SIGCHLD is "ignore."

If "ignore" meant "SIG_IGN", then no child would ever be a zombie, and that's not the case.

It doesn't get much more clear from there:

If the process specifically sets its disposition to SIG_IGN, children of the calling process will not generate zombie processes. Note that this is different from its default action (SIG_DFL), which from Figure 10.1 is to be ignored. Instead, on termination, the status of these child processes is discarded.

I'm having a hard time groking what the impact of the various values (or undefined non-value) are. So far, the solution has been to rotate through those choices until I get the desired behavior, and I'd rather understand exactly how each value defines the behavior of the signal.

The behavior: a child process is calling "system" or using backticks which create another child, and the signal would normally be caught by the wrong (parent) handler. Setting a local handler can work, but I don't understand which value is most appropriate if I want the signal from the grand-child to do nothing.

Could someone please illuminate me?

UPDATE: Based on ikegami's feedback, I did some specific testing. The behavior is, at least partially, platform specific.

Consider the following fragment:

$SIG{CHLD} = sub {
    while( ( my $child = waitpid( -1, &WNOHANG ) ) > 0 ) {
        print "SIGNAL CHLD $child\n";
    }
};

my $pid = fork();

if( ! $pid ) {
    system( 'echo Grandchild PID = $$' );
    sleep 2;
    exit;
}

print "Child PID = $pid\n";
sleep 5;

Perl 5.8.6 on Solaris 10 will display "SIGNAL CHLD" messages for the PID of the system() call. Doing anything, even as trivial as

local $SIG{CHLD};

in the child will suppress those messages.

On every other flavor I tried, the reaper never sees the child.

Answer

mob picture mob · Dec 5, 2011

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.