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

12,375

Solution 1

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

$

Solution 2

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.

Solution 3

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.

Solution 4

$SIG{CHLD} = 'IGNORE'

This is passed to the OS as SIG_IGN, which according to the OS docs, makes the child processes terminate without becoming zombies (or reporting their exit code to the parent).

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

These are all the same at the OS level (they will call signal() with SIG_DFL). However some perl packages, notably AnyEvent::child, will not work when $SIG{CHLD} is set to 'DEFAULT'.

Share:
12,375
Trueblood
Author by

Trueblood

Updated on July 27, 2022

Comments

  • Trueblood
    Trueblood almost 2 years

    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.