【问题标题】:Ignoring signals in parent process忽略父进程中的信号
【发布时间】:2015-09-22 03:55:51
【问题描述】:

我正在尝试实现一个 shell 程序,我希望 shell 程序忽略 SIG_INT(ctrl + c)。但是在我的程序中,孩子也忽略了 SIG_INT 信号,它不应该这样做,因为 exec 应该将子进程带到另一个程序,并且该程序应该默认处理 SIG_INT 信号。当按下 ctrl + c 时,我应该怎么做才能终止子进程。

新编辑:在我将 signal(certain_signal, SIG_DFL) 放入我的子进程的块中后,我的代码工作正常。但我仍然对它是如何工作的感到困惑。这是否意味着信号和信号处置都可以通过执行命令传播?

int main(void){
signal(SIG_INT, SIG_IGN);
int result = fork();
if(result == 0){
    //child:
    //exec some programs
}
else{
    waitpid(result);
    //do something
}

}

【问题讨论】:

  • 你有没有试过移动代码'signal(SIG_INT, SIG_IGN);'到“waitpid”部分之前的地方?
  • @Turtle 我没有,但这应该不是问题,除非执行部分出错,对吧?

标签: c signals


【解决方案1】:

我相信您对exec 如何修改信号配置有一点误解。在 Linux exec 手册页中,例如(1),它指出(我的重点):

execve() 期间保留所有进程属性,但以下情况除外:

  • 正在捕获的任何信号的处置将重置为默认值 (signal(7))。

被捕获的信号与被忽略的信号相同,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;
}

请注意,您还可以更改第一个代码示例中的处置,在 forkexec 之间。如果您实际上不控制第二个代码示例将做什么(例如,如果您正在调用一个您没有编译的可执行文件),这将是更可取的。

运行qqp,无论你按多少次CTRL-C,父子都不会提前退出。但是,取消注释掉恢复默认行为的行,您就可以轻松地摆脱孩子。

因此,如果您希望您的孩子恢复默认行为,您需要在孩子本身中执行此操作,例如:

signal (SIG_INT, SIG_DFL);

(1)POSIX has a little more detail 关于会发生什么:

在调用过程映像中设置为默认操作 (SIG_DFL) 的信号应设置为新过程映像中的默认操作。 除了SIGCHLD,被调用进程映像设置为忽略(SIG_IGN)的信号应设置为被新进程映像忽略。 设置为被调用进程映像捕获的信号应设置为新过程映像中的默认操作(请参阅&lt;signal.h&gt;)。如果 SIGCHLD 信号被设置为被调用进程映像忽略,则未指定 SIGCHLD 信号是被设置为忽略还是设置为新进程映像中的默认操作。


而且,根据您的编辑,我提出的解决方案有效,但它为您提出了另一个问题:

这是否意味着信号和信号处置都可以通过执行命令传播?

信号本身不会通过exec 调用传播,信号实际上是正在生成的“中断”。这与信号处理程序(处理信号的代码)或信号处置(信号发生时的处理)不同。如上所示,处置可能会在exec 调用中幸存下来,但处理程序不能。信号也没有。

当您按下 CTRL-C 并且多个进程受到影响时,您所看到的与跨越 exec 边界的继承信号无关,更多的是与终端有关。

传递给单个进程的信号不会影响其任何子进程。但是,按下 CTRL-C 并不会 向单个进程发送信号。 POSIX终端接口有一个控制终端和进程组的概念:

每个进程也是进程组的成员。每个终端设备记录一个进程组,称为其前台进程组。进程组控制终端访问和信号传递。 在终端生成的信号被发送到属于终端前台进程组成员的所有进程。

【讨论】:

  • 但是如果我通过程序/bin/ls来执行函数,我的子进程的代码应该被丢弃并被/bin/ls程序的代码替换吧?然后 /bin/ls 程序中编写的处理程序应该在那里处理来自终端的中断(信号)。我仍然对 execv 的想法有误吗?顺便说一句,我在我的代码中使用 execv 而不是 execve,
  • @lplouis,最后一点,他们最终都调用了相同的最终函数,所以没关系。如果你执行ls,是的,所有处理程序要么变成SIG_DFL 或SIG_IGN,但请记住那些不是处理程序。处理程序是将在传递的信号上运行的代码。通过 exec 替换代码,处理程序不再有意义,这就是它们被重置的原因。但是, SIG_DFL 和 SIG_IGN 可以在 exec 中存活,因为它们不涉及处理程序。对于ls,除非将SIGINT 重置为SIG_DFL,否则它将保持为SIG_IGN,因为您在执行之前设置它。我会添加更多细节。
  • 为什么要在子进程中重置信号配置而不是仅在父进程中设置处理程序?
  • @Filipe,您无法从父级更改子级配置,您可以做的最好的事情是分叉后但执行前,但它仍然是子进程在做这件事。假设这就是您的意思,这是一种可能性,并且可能会更好,因为此时它仍在运行与父级相同的代码。我将其添加为选项。相反,如果您的意思是在您执行之前不要设置父处理程序,那么父处理程序可能在之前做了很多工作,这些工作需要忽略 INT 信号。跨度>
猜你喜欢
  • 2012-10-08
  • 1970-01-01
  • 2023-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-15
  • 1970-01-01
相关资源
最近更新 更多