【问题标题】:calling signal after fork分叉后调用信号
【发布时间】:2011-06-17 05:58:03
【问题描述】:

“代码清单 1”和“代码清单 2”有什么区别吗?因为在代码清单 1 中,子进程能够捕捉到 SIGTERM 信号并很好地退出。但是代码列表 2 在 SIGTERM 信号上突然终止。

我正在使用 Linux 和 C。

代码清单 1

if (signal(SIGTERM, stopChild) == SIG_ERR) {
    printf("Could not attach signal handler\n");
    return EXIT_FAILURE;
}
pid = fork();

代码清单 2

pid = fork();
if (signal(SIGTERM, stopChild) == SIG_ERR) {
    printf("Could not attach signal handler\n");
    return EXIT_FAILURE;
}

奇怪的是,在代码清单 2 中,子进程和父进程都为 SIGTERM 设置了信号处理程序。所以,这应该有效。不是吗?

【问题讨论】:

  • 我刚刚测试过,对我来说很好用。在这两种情况下,两个进程都通过 stopChild() 调用优雅地退出。
  • 你是否有机会从线程中调用 fork()?
  • 你能提供一个完整的示例程序来展示这种行为吗?
  • 谁发送 SIGTERM?这不是父母,是吗? 咳嗽比赛咳嗽(如果答案是肯定的)
  • 今天无法用相同的代码重现它!不知道发生了什么。

标签: c linux fork signals signal-handling


【解决方案1】:

如果您从父级发送 SIGTERM,最终结果取决于进程的调度顺序。

如果孩子先被安排,一切正常:

                                                +---------------+
                                                | pid = fork(); |
                                                +-------+-------+
                   parent                               |                               child
                          +-----------------------------+-----------------------------+
                          |                                                           |
                          |                                 +-------------------------+--------------------------+
                          |                                 | if (signal(SIGTERM, stopChild) == SIG_ERR) {       |
                          |                                 |       printf("Could not attach signal handler\n"); |
                          |                                 |       return EXIT_FAILURE;                         |
                          |                                 | }                                                  |
                          |                                 +-------------------------+--------------------------+
                          |                                                           |
                          .                                                           .
                          .                                                           .
                          .                                                           .
                          |                                                           |
+-------------------------+--------------------------+                                |
| if (signal(SIGTERM, stopChild) == SIG_ERR) {       |                                |
|       printf("Could not attach signal handler\n"); |                                |
|       return EXIT_FAILURE;                         |                                |
| }                                                  |                                |
+-------------------------+--------------------------+                                |
                          |                                                           |
                          |                                                           |
                          |                                                           |
            +-------------+-------------+                                             |
            | if (pid > 0) {            |                                             |
            |       kill(pid, SIGTERM); |                                             |
            | }                         |                                             |
            +-------------+-------------+                                             |
                          |                                                           |
                          |                                                           |
                          |                                                           |

但是如果先安排了paren,孩子可能没有时间设置信号处理程序:

                                                +---------------+
                                                | pid = fork(); |
                                                +-------+-------+
                   parent                               |                               child
                          +-----------------------------+-----------------------------+
                          |                                                           |
+-------------------------+--------------------------+                                |
| if (signal(SIGTERM, stopChild) == SIG_ERR) {       |                                |
|       printf("Could not attach signal handler\n"); |                                |
|       return EXIT_FAILURE;                         |                                |
| }                                                  |                                |
+-------------------------+--------------------------+                                |
                          |                                                           |
                          |                                                           |
                          |                                                           |
            +-------------+-------------+                                             |
            | if (pid > 0) {            |                                             |
            |       kill(pid, SIGTERM); |                                             |
            | }                         |                                             |
            +-------------+-------------+                                             |
                          |                                                           |
                          .                                                           .
                          .                                                           .
                          .                                                           .
                          |                                                           |
                          |                                 +-------------------------+--------------------------+
                          |                                 | if (signal(SIGTERM, stopChild) == SIG_ERR) {       |
                          |                                 |       printf("Could not attach signal handler\n"); |
                          |                                 |       return EXIT_FAILURE;                         |
                          |                                 | }                                                  |
                          |                                 +-------------------------+--------------------------+
                          |                                                           |
                          |                                                           |
                          |                                                           |

这称为竞态条件,因为最终结果取决于谁先运行。

【讨论】:

  • 我不认为这是问题所在,因为父进程在发送信号之前被赋予了 10 秒的睡眠时间。无论如何,我现在接受这个作为答案,因为我无法重现这个问题。
【解决方案2】:

首先,不推荐使用signal(),最好使用sigaction()。我不认为 fork() 有完全消失的危险,因为很多东西都在使用它,但是 sigaction() 确实提供了一个更好的接口。

您遇到的行为通常是由在线程内调用 fork() 引起的。 POSIX 解决了这个specifically

一个进程应该创建一个 单线程。如果是多线程 进程调用fork(),新进程 应包含调用的副本 线程及其整个地址空间, 可能包括 互斥锁和其他资源。 因此,为避免错误, 子进程只能执行 异步信号安全操作,直到 诸如执行功能之一的时间 叫做。 [THR] 分叉处理程序可以 通过建立 pthread_atfork() 函数,以便 维护应用程序不变量 fork() 调用。

当应用程序从 信号处理程序和任何分叉 注册的处理程序 pthread_atfork() 调用一个函数 不是异步信号安全的, 行为未定义。

这意味着,您不会继承父级整个地址空间的副本,而是仅继承调用线程地址空间的副本,其中不包含您的处理程序。可以想象,您确实(甚至可能在不知情的情况下)从线程内调用 fork()。

子进程获得父地址空间的副本。与信号的唯一区别是 pending 信号,子级将不会收到这些信号,因为它会将信号集初始化为零。但是,是的,它确实获得了处理程序的副本。

【讨论】:

  • 在 fork 之前调用信号实际上是成功的,因此信号配置确实被复制到孩子,在 fork 之后调用它是中断的情况,即在孩子中以某种方式调用信号不起作用。
  • @wich - 这就是为什么我非常怀疑在线程中调用了 fork()。
【解决方案3】:

好吧,根据 man fork:

fork()、fork1() 和 forkall() 函数创建一个新进程。新进程(子进程)的地址空间是调用进程(父进程)地址空间的精确副本。子进程从父进程继承以下属性:

...

o信号处理设置(即SIG_DFL、SIG_IGN、SIG_HOLD、函数地址)

在第一个示例中,信号处理程序将从父上下文复制到分叉子代。但我无法解释为什么在第二个示例中设置子信号处理程序会失败。

【讨论】:

  • 但在第二个示例中,两个进程都调用了 signal()。所以它也应该为子进程设置信号处理程序,不是吗?
  • @Sergey,这正是我的疑问!
  • POSIX 表示父级的任何 pending 信号都不会发送给子级,它从初始化为零的信号集开始。但是,孩子应该继承处理程序。但是,在没有 pthread_atfork() 的情况下在线程中调用 fork() 可能会解释 OP 正在经历什么。
  • @Sergey, @Sabya;确实,仍然不确定是什么导致了问题。
  • 我不知所措,将(编辑的)答案留在叉子上以供参考。
猜你喜欢
  • 2012-11-30
  • 1970-01-01
  • 2014-06-25
  • 2011-01-17
  • 1970-01-01
  • 2012-09-23
  • 2018-10-13
  • 2015-08-24
  • 2015-08-19
相关资源
最近更新 更多