我相信您对exec 如何修改信号配置有一点误解。在 Linux exec 手册页中,例如(1),它指出(我的重点):
execve() 期间保留所有进程属性,但以下情况除外:
被捕获的信号不与被忽略的信号相同,signal 手册页证明了这一点:
使用这些系统调用,进程可以选择在传递信号时发生以下行为之一:
- 执行默认操作;
-
忽略信号;或
-
使用信号处理程序捕获信号,这是一个程序员定义的函数,在传递信号时会自动调用。
这实际上是有道理的,因为虽然忽略信号可以通过 exec 调用传播,但信号处理程序不能 - 用于处理信号的函数已被 exec 调用替换,因此尝试说它很可能是灾难性的。
通过编译以下两个程序qqp.c,您可以看到这种继承“忽略”处置的行为:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
int main (void) {
signal (SIGINT, SIG_IGN);
puts("Parent start");
if (fork() == 0)
execl ("./qqc", 0);
wait(0);
sleep (1);
puts("Parent end");
return 0;
}
和qqc.c:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main (void) {
//signal (SIGINT, SIG_DFL);
puts("Child start");
sleep (60);
puts("Child end");
return 0;
}
请注意,您还可以更改第一个代码示例中的处置,在 fork 和 exec 之间。如果您实际上不控制第二个代码示例将做什么(例如,如果您正在调用一个您没有编译的可执行文件),这将是更可取的。
运行qqp,无论你按多少次CTRL-C,父子都不会提前退出。但是,取消注释掉恢复默认行为的行,您就可以轻松地摆脱孩子。
因此,如果您希望您的孩子恢复默认行为,您需要在孩子本身中执行此操作,例如:
signal (SIG_INT, SIG_DFL);
(1)POSIX has a little more detail 关于会发生什么:
在调用过程映像中设置为默认操作 (SIG_DFL) 的信号应设置为新过程映像中的默认操作。 除了SIGCHLD,被调用进程映像设置为忽略(SIG_IGN)的信号应设置为被新进程映像忽略。 设置为被调用进程映像捕获的信号应设置为新过程映像中的默认操作(请参阅<signal.h>)。如果 SIGCHLD 信号被设置为被调用进程映像忽略,则未指定 SIGCHLD 信号是被设置为忽略还是设置为新进程映像中的默认操作。
而且,根据您的编辑,我提出的解决方案有效,但它为您提出了另一个问题:
这是否意味着信号和信号处置都可以通过执行命令传播?
信号本身不会通过exec 调用传播,信号实际上是正在生成的“中断”。这与信号处理程序(处理信号的代码)或信号处置(信号发生时的处理)不同。如上所示,处置可能会在exec 调用中幸存下来,但处理程序不能。信号也没有。
当您按下 CTRL-C 并且多个进程受到影响时,您所看到的与跨越 exec 边界的继承信号无关,更多的是与终端有关。
传递给单个进程的信号不会影响其任何子进程。但是,按下 CTRL-C 并不会 向单个进程发送信号。 POSIX终端接口有一个控制终端和进程组的概念:
每个进程也是进程组的成员。每个终端设备记录一个进程组,称为其前台进程组。进程组控制终端访问和信号传递。 在终端生成的信号被发送到属于终端前台进程组成员的所有进程。