当发送到包含子项的 Perl 脚本时,SIGINT (^C) 会发生什么情况?

发布于 2024-10-12 07:53:49 字数 932 浏览 3 评论 0原文

我有一个可以分叉的 Perl 脚本。

每个分支运行一个外部程序,解析输出,并将输出转换为可存储文件。

然后,父级读入可存储文件,并分析每个子级的总数据,然后继续重复前一个分支,否则父级将停止。

当我发出 ^C 而一些孩子仍在运行外部程序时,到底会发生什么?父 Perl 脚本在前台被调用,并且我认为尽管有分叉,它仍保留在前台。

SIGINT是否传递给所有子进程,即父进程、父进程的子进程以及子进程调用的外部程序?

更新

我应该补充一点,当我发出 SIGINIT 时,我的脚本子级调用的外部程序似乎确认了该信号并终止。但孩子们,或者也许是家长计划,仍在继续。这对我来说都是不清楚的。

更新2

关于tchrist的评论,外部程序是用Perl的system()命令调用的。

事实上,tchrist的评论似乎也包含了我一直在寻找的解释。经过更多调试后,根据我的程序的行为,似乎 SIGINT 确实从父级传递到所有子级,并从所有子级传递到其所有子级(外部程序)。

因此,根据 tchrist 的评论,似乎正在发生的事情是 CTRL-C 正在杀死外部程序,导致子级移出 system() 命令 - 仅此而已。

虽然我让我的孩子检查 system() 中调用的退出状态,但我假设 CTRL-C 会杀死从父级开始的所有内容,而不是导致创建更多轮次处理,这就是发生的事情!

解决方案(针对我的问题)

我只需在父级中为 SIGINT 创建一个信号处理程序。然后,信号处理程序将向每个子级发送 SIGTERM(我认为也会向子级的子级发送 SIGTERM),然后导致父级正常退出。虽然这个有点明显的解决方案可能会解决一些问题,但我想了解我对 SIGINT 在 Perl 中 fork 的行为的误解。

I have a Perl script that forks.

Each fork runs an external program, parses the output, and converts the output to a Storable file.

The Storable files are then read in by the parent and the total data from each of the children are analyzed before proceeding onto a repeat of the previous fork or else the parent stops.

What exactly happens when I issue a ^C while some of the children are still running the external program? The parent perl script was called in the foreground and, I presume, remained in the foreground despite the forking.

Is the SIGINT passed to all children, that is, the parent, the parent's children, and the external program called by the children??

UPDATE:

I should add, it appears that when I issue the SIGINIT, the external program called by the children of my script seem to acknowledge the signal and terminate. But the children, or perhaps the parent program, carry on. This is all unclear to me.

UPDATE 2:

With respect to tchrist's comment, the external program is called with Perl's system() command.

In fact, tchrist's comment also seems to contain the explanation I was looking for. After some more debugging, based on the behavior of my program, it appears that, indeed, SIGINT is being passed from the parent to all children and from all children to all of their children (the external program).

Thus, what appears to be happening, based on tchrist's comment, is that CTRL-C is killing the external program which causes the children to move out of the system() command - and nothing more.

Although I had my children check the exit status of what was called in system(), I was assuming that a CTRL-C would kill everything from the parent down, rather than lead to the creation of more rounds of processing, which is what was happening!!!

SOLUTION (to my problem):

I need to just create a signal handler for SIGINT in the parent. The signal handler would then send SIGTERM to each of the children (which I presume would also send a SIGTERM to the children's children), and then cause the parent to exit gracefully. Although this somewhat obvious solution likely would have fixed things, I wanted to understand my misconception about the behavior of SIGINT with respect to forking in Perl.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

打小就很酷 2024-10-19 07:53:49

就信号而言,Perl 的内置 system 函数的工作方式与标准 C 库中的 C system(3) 函数类似。如果您使用 Perl 版本的 system() 或管道打开或反引号,则父级(调用 system 的那个而不是被它调用的那个)将忽略任何 SIGINT并在孩子们跑步时发出 SIGQUIT 信号。如果您使用 fork?wait:exec 三重奏的某些变体自行推出,那么您必须自己考虑这些问题。

考虑一下当您使用 system("vi somefile") 并在 vi 中进行长时间搜索时点击 ^C 时会发生什么:只有 vi 接受 (非致命)SIGINT;家长忽略它。这是正确的行为。这就是 C 如此工作的原因,也是 Perl 如此工作的原因。

您必须记住的是,仅仅因为 ^C 向前台进程组中的所有进程(甚至是具有不同有效 UID 或 GID 的进程)发送 SIGINT,并不意味着它会导致所有进程那些进程退出。 ^C 只是一个 SIGINT,意味着中断进程,而不是 SIGKILL,意味着不问任何问题就终止。

有很多种程序,在没有警告的情况下直接杀死是错误的;编辑只是这样的一个例子。邮寄者可能是另一个。对此要格外小心。

许多类型的程序选择性地忽略、捕获或阻止(意味着延迟传递)各种类型的信号。只有 SIGINT 的默认行为是导致进程退出。您可以使用传统操作系统上的此类代码来查明这种情况是否发生,以及实际上是哪个信号导致它发生(除其他外):

if ($wait_status = system("whatever")) {
    $sig_killed   = $wait_status & 127;
    $did_coredump = $wait_status & 128;
    $exit_status  = $wait_status >>  8;
    # now do something based on that...
}

请仔细注意,^C'd vi,例如,将不会有一个等待状态字,表明它因未捕获的SIGINT而死亡,因为没有一个等待状态字:它捕获了它。

有时你的孩子会背着你生下自己的孩子。凌乱但真实。因此,有时我会以这种方式种族灭绝所有已知和未知的后代:

# scope to temporize (save+restore) any previous value of $SIG{HUP}
{
    local $SIG{HUP} = "IGNORE";
    kill HUP => -$;   # the killpg(getpid(), SIGHUP) syscall
}

这当然不适用于 SIGKILL 或 SIGSTOP,它们不适合被这样忽略。

您可能需要注意的另一件事是,在 5.8 版本之前,Perl 中的信号处理在历史上并不是一种可靠的安全操作。现在是,但这是一个与版本相关的问题。如果您还没有这样做,那么您一定应该阅读 perlipc 联机帮助页 中的延迟信号,也许还有 PERL_SIGNALS 位于 perlrun 联机帮助页 中。

Perl’s builtin system function works just like the C system(3) function from the standard C library as far as signals are concerned. If you are using Perl’s version of system() or pipe open or backticks, then the parent — the one calling system rather than the one called by it — will IGNORE any SIGINT and SIGQUIT while the children are running. If you’ve you rolled your own using some variant of the fork?wait:exec trio, then you must think about these matters yourself.

Consider what happens when you use system("vi somefile") and hit ^C during a long search in vi: only vi takes a (nonfatal) SIGINT; the parent ignores it. This is correct behavior. That’s why C works this way, and that’s why Perl works this way.

The thing you have to remember is that just because a ^C sends a SIGINT to all processes in the foregrounded process group (even those of differing effective UID or GID), that does not mean that it causes all those processes to exit. A ^C is only a SIGINT, meant to interrupt a process, not a SIGKILL, meant to terminate with no questions asked.

There are many sorts of program that it would be wrong to just kill off with no warning; an editor is just one such example. A mailer might be another. Be exceedingly careful about this.

Many sorts of programs selectively ignore, trap, or block (means delay delivery of) various sorts of signals. Only the default behavior of SIGINT is to cause the process to exit. You can find out whether this happened, and in fact which signal caused it to happen (amongst other things), with this sort of code on traditional operating systems:

if ($wait_status = system("whatever")) {
    $sig_killed   = $wait_status & 127;
    $did_coredump = $wait_status & 128;
    $exit_status  = $wait_status >>  8;
    # now do something based on that...
}

Note carefully that a ^C’d vi, for example, will not have a wait status word indicating it died from an untrapped SIGINT, since there wasn’t one: it caught it.

Sometimes your kids will go and have kids of their own behind your back. Messy but true. I have therefore been known, on occasion, to genocide all progeny known and unknown this way:

# scope to temporize (save+restore) any previous value of $SIG{HUP}
{
    local $SIG{HUP} = "IGNORE";
    kill HUP => -$;   # the killpg(getpid(), SIGHUP) syscall
}

That of course doesn’t work with SIGKILL or SIGSTOP, which are not amenable to being IGNOREd like that.

Another matter you might want to be careful of is that before the 5.8 release, signal handling in Perl has not historically been a reliably safe operation. It is now, but this is a version-dependent issue. If you haven’t yet done so, then you should definitely read up on deferred signals in the perlipc manpage, and perhaps also on the PERL_SIGNALS envariable in the perlrun manpage.

誰認得朕 2024-10-19 07:53:49

当您在终端窗口(或任何终端)中点击 ^C 时,它将向该终端中的前台进程组发送 SIGINT。现在,当您从命令行启动程序时,它通常位于其自己的进程组中,并且成为前台进程组。默认情况下,当您分叉一个子进程时,它将与父进程位于同一进程组中,因此默认情况下,父进程(从命令行调用的顶级程序)、其所有子进程、子进程的子进程等,以及由其中任何一个(都只是子进程)调用的任何外部程序都将位于同一进程组中,因此都将收到 SIGINT 信号。

但是,如果这些子进程或程序中的任何一个调用 setpgrp 或 setpgid 或 setsid,或者导致进程成为新进程组中的任何其他调用,则这些进程(以及它们在离开前台进程组后启动的任何子进程)将不会接收 SIGINT。

此外,当进程收到 SIGINT 时,它可能不会终止——它可能会忽略该信号,或者可能会捕获该信号并执行完全不同的操作。 SIGINT 的默认信号处理程序会终止进程,但这只是可以覆盖的默认值。

编辑

从您的更新来看,听起来一切都保留在同一个进程组中,因此信号正在传递给每个人,因为孙子(外部程序)正在退出,但孩子们正在捕捉并忽略 SIGINT。根据 tchrist 的评论,这听起来像是 perl 的默认行为。

When you hit ^C in a terminal window (or any terminal, for that matter), it will send a SIGINT to the foreground process GROUP in that terminal. Now when you start a program from the command line, its generally in its own process group, and that becomes the foreground process group. By default, when you fork a child, it will be in the same process group as the parent, so by default, the parent (top level program invoked from the command line), all its children, children's children, etc, as well as any external programs invoked by any of these (which are all just children) will all be in that same process group, so will all receive the SIGINT signal.

However, if any of those children or programs call setpgrp or setpgid or setsid, or any other call that causes the process to be a in a new progress group, those processes (and any children they start after leaving the foreground process group) will NOT receive the SIGINT.

In addition, when a process receives a SIGINT, it might not terminate -- it might be ignoring the signal, or it might catch it and do something completely different. The default signal handler for SIGINT terminates the process, but that's just the default which can be overridden.

edit

From your update, it sounds like everything is remaining in the same process group, so the signals are being delivered to everyone, as the grandchildren (the external programs) are exiting, but the children are catching and ignoring the SIGINTs. By tchrist's comment, it sounds like this is the default behavior for perl.

过潦 2024-10-19 07:53:49

如果您终止父进程,子进程(以及正在运行的外部程序)仍将运行,直到它们以某种方式终止。如果父进程有一个信号处理程序来捕获 SIGINT,然后杀死进程组(通常是父进程的 pid),则可以避免这种情况。这样,一旦父进程收到 SIGINT,它就会杀死所有的子进程。

因此,要回答您的问题,这完全取决于信号处理程序的实现。根据您的更新,似乎父级确实杀死了它的子级,然后它没有终止自己,而是回去做其他事情(将其视为重置而不是完全关闭/启动组合)。

If you kill the parent process, the children (and external programs running) will still run until they terminate one way or another. This can be avoided if the parent has a signal handler that catches the SIGINT, and then kills the process group (often parent's pid). That way, once the parent gets the SIGINT, it will kill all of it's children.

So to answer your question, it all depends on the implementation of the signal handler. Based on your update, it seems that the parent indeed kills it's children and then instead of terminating itself, it goes back to doing something else (think of it as a reset instead of a complete shutdown/start combination).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文