使用系统时信号传播到父母

时间:2016-01-14 17:04:55

标签: perl

我编写了一个使用system()启动另一个脚本的包装脚本。子脚本陷阱SIGINT并在内部处理异常。因此,它在退出时不应将SIGINT传播到其父级。但是,在某些情况下,父级仍然会收到SIGINT。例如(parent.pl):

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

my @data = (q(child.pl 'dummy'), q(child.pl), q(bash -c child.pl), q(sh -c child.pl));

for ( @data ) {
    say "Running command '$_'..";
    my $res = system $_;
    my $signal = $res & 127;
    my $rval = $res >> 8;
    say "Parent received return value: $rval";
    if ( $signal == POSIX::SIGINT ) {
        say "Parent received SIGINT";
    }
}

child.pl

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

eval {
    local $SIG{INT} = sub { die "Aborted by user.\n" };
    sleep 10;
};
if ( $@ ) {
    print "\n" . $@;
    exit 0;
}
say "Timed out..";
exit 1;

如果我在超时前按 CTRL-C ,输出如下:

Running command 'child.pl 'dummy''..
^C
Aborted by user.
Parent received return value: 0
Parent received SIGINT
Running command 'child.pl'..
^C
Aborted by user.
Parent received return value: 0
Running command 'bash -c child.pl'..
^C
Aborted by user.
Parent received return value: 0
Running command 'sh -c child.pl'..
^C
Aborted by user.
Parent received return value: 0
Parent received SIGINT

因此,在第一种情况和最后一种情况下,父母会收到SIGINT,而对于第二种和第三种情况则不会。{/ p>

这是什么原因?如何修复,以便SIGINT不会传播给第一个和最后一个案例?

(我怀疑它与Shell的类型有关,即sh vs bash

1 个答案:

答案 0 :(得分:2)

首先,让我们了解正在执行的内容。

system($shell_command)

的缩写
system({ "/bin/sh" } "/bin/sh", "-c", $shell_command)

除非shell命令不包含shell元字符,但包含空格,在这种情况下

system($shell_command)

的缩写
my @cmd = split(' ', $shell_command);
system({ $cmd[0] } @cmd)

因此,

system("child.pl 'dummy'")   is short for   system({ "/bin/sh" } "/bin/sh", "-c", "child.pl 'dummy'")
system("child.pl")           is short for   system({ "child.pl" } "child.pl")
system("bash -c child.pl")   is short for   system({ "bash" } "bash", "-c", "child.pl")
system("sh -c child.pl")     is short for   system({ "sh" } "sh", "-c", "child.pl")

值得注意的是,bash替换为child.pl,而不是在此特定情况下在单独的过程中生成它。这使得child.pl在第三种情况下成为parent.pl的直接子女(就像第二种情况一样)。

其次,让我们知道Ctrl-C的作用。

当按下Ctrl-C时,终端将SIGINT发送到将该终端作为其控制终端的每个进程。换句话说,SIGINT被发送到会话的每个进程。

正如您在system("ps -o pid,ppid,pgrp,sid,cmd");添加child.pl所看到的那样,根据测试用例,我们的会话中有三到四个进程。

  • child.plchild.pl处理SIGINT。它并没有被它杀死。
  • 在测试用例1和4中由parent.pl启动的shell:shell被SIGINT杀死。
  • parent.plsystem相当于local $SIG{INT} = 'IGNORE';,因此忽略了SIGINT。
  • 启动parent.pl的登录shell:它忽略了SIGINT,但我不确定原因。我猜它是因为它是一个交互式shell。

所以,这就是你所观察到的:

  • parent.pl的(直接)子项为child.pl [测试用例2和3]时,子项(child.pl)不会因为处理SIGINT而死于SIGINT。
  • parent.pl的(直接)子节点是shell [测试用例1和4]时,子节点(shell)从SIGINT中消失,因为非交互式shell不会处理/忽略SIGINT。