Return value from system() when using SIGI

2019-06-25 10:12发布

问题:

I am experiencing some strange return values from system() when a child process receives a SIGINT from the terminal. To explain, from a Perl script parent.pl I used system() to run another Perl script as a child process, but I also needed to run the child through the shell, so I used the system 'sh', '-c', ... form.. So the parent of the child became the sh process and the parent of the sh process became parent.pl. Also, to avoid having the sh process receiving the SIGINT signal, I trapped it.

For example, parent.pl:

use feature qw(say);
use strict;
use warnings;

for (1..3) {
    my $res = system 'sh', '-c', "trap '' INT; child$_.pl";
    say "Parent received return value: " . ($res >> 8);
}

where child1.pl:

local $SIG{INT} = "DEFAULT";
sleep 10;
say "Child timed out..";
exit 1;

child2.pl:

local $SIG{INT} = sub { die };
sleep 10;
say "Child timed out..";
exit 1;

and child3.pl is:

eval {
    local $SIG{INT} = sub { die };
    sleep 10;
};
if ( $@ ) {
    print $@;
    exit 2;
}
say "Child timed out..";
exit 0;

If I run parent.pl (from the command line) and press CTRL-C to abort each child process, the output is:

^CParent received return value: 130
^CDied at ./child2.pl line 7.
Parent received return value: 4
^CDied at ./child3.pl line 8.
Parent received return value: 2

Now, I would like to know why I get a return value of 130 for case 1, and a return value of 4 for case 2.

Also, it would be nice to know exactly what the "DEFAULT" signal handler does in this case.

Note: the same values are returned if I replace sh with bash ( and trap SIGINT instead of INT in bash ).

See also:

  • Propagation of signal to parent when using system
  • perlipc
  • Chapter 15, in Programming Perl, 4th Edition

回答1:

This question is very similar to Propagation of signal to parent when using system that you asked earlier.

From my bash docs:

When a command terminates on a fatal signal N, bash uses the value of 128+N as the exit status.

SIGINT is typically 2, so 128 + 2 give you 130.

Perl's die figures out its exit code by inspecting $! or $? for an uncaught exception (so, not the case where you use eval):

exit $! if $!;              # errno
exit $? >> 8 if $? >> 8;    # child exit status
exit 255;                   # last resort

Notice that in this case, Perl exits with the value as is, not shifted up 8 bits.

The errno value happens to be 4 (see errno.h). The $! variable is a dualvar with different string and numeric values. Use it numerically (like adding zero) to get the number side:

use v5.10;

local $SIG{INT}=sub{
    say "numeric errno is ", $!+0;
    die
    };
sleep 10;
print q(timed out);
exit 1;

This prints:

$ bash -c "perl errno.pl"
^Cnumeric errno is 4
Died at errno.pl line 6.
$ echo $?
4


回答2:

Taking your questions out of order:

Also, it would be nice to know exactly what the "DEFAULT" signal handler does in this case.

Setting the handler for a given signal to "DEFAULT" affirms or restores the default signal handler for the given signal, whose action depends on the signal. Details are available from the signal(7) manual page. The default handler for SIGINT terminates the process.

Now, I would like to know why I get a return value of 130 for case 1, and a return value of 4 for case 2.

Your child1 explicitly sets the default handler for SIGINT, so that signal causes it to terminate abnormally. Such a process has no exit code in the conventional sense. The shell also receives the SIGINT, but it traps and ignores it. The exit status it reports for the child process (and therefore for itself) reflects the signal (number 2) that killed the child.

Your other two child processes, on the other hand, catch SIGINT and terminate normally in response. These do produce exit codes, which the shell passes on to you (after trapping and ignoring the SIGINT). The documentation for die() describes how the exit code is determined in this case, but the bottom line is that if you want to exit with a specific code then you should use exit instead of die.