【问题标题】:when multi-thread program receive a SIGPIPE signal because send, which thread would handle the signal in linux?当多线程程序因为发送而接收到 SIGPIPE 信号时,Linux 中哪个线程会处理该信号?
【发布时间】:2023-03-30 11:36:03
【问题描述】:

如果一个发送导致一个 SIGPIPE 信号,哪个头会处理它?发送的线程还是随机线程?也就是说Linux系统通过kill或者pthread_kill发送信号?

【问题讨论】:

    标签: multithreading signals


    【解决方案1】:

    SIGPIPE 这样的异步信号可以发送到任何线程。您可以使用信号掩码来限制哪些线程符合条件。

    SIGSEGV 等同步信号将在导致它们的线程上传递。

    【讨论】:

      【解决方案2】:

      总结

      这个问题的答案有两个方面:所讨论的系统应该如何运行以及它实际上如何运行。

      由于大多数程序员希望 Linux 大部分与 POSIX 兼容,我们可以研究该标准,它实际上明确地指定了行为——信号直接发送到执行写入的线程。但是 Linux 是否坚持这一点尚不清楚,Linux 文档在这里也没有帮助。对 Linux 行为的检查表明它符合 POSIX,但并未证明这一点,阅读源代码为我们提供了有关当前 Linux 版本的必要证据。

      tl;dr:它总是由执行写入的线程处理。

      POSIX 标准

      POSIX 标准要求(自 IEEE 标准 1003.1-2001/Cor 2-2004 起)将由于写入没有读取器的管道而生成的 SIGPIPE 传送到执行写入的线程。见EPIPE in the ERRORS section of the description of write()(强调我的):

      [EPIPE] 尝试写入未打开以供任何进程读取的管道或 FIFO,或者仅打开一端。 SIGPIPE 信号也应发送到线程

      Linux 文档

      也就是说,目前尚不清楚 Linux 是否正确处理了这个问题。 man 7 signal 页面没有给出线程和进程导向信号的具体列表,只是示例,它对线程导向信号的定义不包括 SIGPIPE:

      信号可能是线程导向的,因为它是由于执行触发硬件异常的特定机器语言指令而生成的 […]

      SIGPIPE 不是特定指令的结果,也不是由硬件异常触发的。

      Glibc 文档根本没有讨论内核生成的同步线程导向信号(即,甚至 SIGSEGV 或 SIGBUS 都没有被讨论为线程导向的),并且有多年前的 bugs in NPTL 报告,尽管这些可能在此期间已修复。

      可观察的 Linux 行为

      我编写了一个程序,它产生一个线程,它使用pthread_sigmask 阻塞 SIGPIPE,创建一个管道对,关闭读取端并将一个字节写入写入端。如果信号是线程导向的,那么在信号再次解除阻塞之前什么都不会发生。如果信号是进程导向的,主线程应该处理信号并且进程应该终止。原因再次来自 POSIX:如果有一个线程的(进程导向的)信号未阻塞,它should be delivered there instead of queueing

      为进程生成的信号应准确地传送到进程中 [...] 没有阻止信号传送的那些线程之一。如果 [...] 进程内的所有线程都阻塞了信号的传递,则信号应在进程上保持挂起状态,直到 […] 线程解除对信号的传递的阻塞,或与信号相关的操作设置为忽略信号。

      我的实验表明,在具有最新 Glibc 的现代 (2020) Linux 上,信号确实被定向到执行写入的线程,因为在写入线程中使用 pthread_sigmask 阻止它会阻止 SIGPIPE 被传递,直到它被解除阻塞。

      Linux 5.4.28 源码

      上面观察到的行为并不能证明什么,因为 Linux 完全有可能只是在几个地方违反了 POSIX,并且信号传递取决于我没有考虑到的一些因素。为了获得我们寻求的证据,我们可以阅读源代码。当然,这只会告诉我们当前的行为,而不是预期的行为——但如果我们发现当前的行为符合 POSIX,它可能会继续存在。

      免责声明:我不是内核黑客,以下是粗略阅读源代码的结果。我可能错过了一些重要的事情。

      kernel/signal.c 中,有一个SYNCHRONOUS_MASK 列出了特殊处理的同步信号。它们是 SIGSEGV、SIGBUS、SIGILL、SIGTRAP、SIGFPE 和 SIGSYS——SIGPIPE 不在列表中。然而,这并不能回答这个问题——它可以是线程导向的而不是同步的。

      那么 SIGPIPE 是如何发送的呢?它源自fs/pipe.c 中的pipe_write(),它在task_struct current 上调用send_sig()current 的使用已经暗示该信号是线程导向的,但让我们继续。 send_sig() 函数在 kernel/signal.c 中定义,并通过一些间接调用最终使用 pid_type type = PIDTYPE_PID 调用 __send_signal()

      在 Linux 术语中,PID refers to a single thread。果然,有了这些参数,挂起的信号列表是线程特定的,而不是共享的;而complete_signal()(在函数末尾调用)甚至不会尝试找到要唤醒的线程,它只是返回,因为已经选择了线程。我不完全了解信号队列是如何工作的,但似乎队列是每个线程的,因此当前线程是获取信号的线程。

      【讨论】:

        猜你喜欢
        • 2014-03-27
        • 2017-12-16
        • 2012-07-25
        • 2023-03-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-09-26
        • 2016-03-23
        相关资源
        最近更新 更多