当多线程程序收到一个SIGPIPE信号因为send,哪个线程会处理linux中的信号?

时间:2014-02-12 03:57:15

标签: multithreading signals

如果发送导致SIGPIPE信号,thead会处理它?发送的线程还是随机线程?换句话说,Linux系统通过kill或pthread_kill发送信号?

2 个答案:

答案 0 :(得分:1)

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

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

答案 1 :(得分:1)

摘要

这个问题的答案有两个方面:相关系统应如何运行以及其实际运行方式。

由于大多数程序员期望Linux主要与POSIX兼容,因此我们可以研究该标准,该标准实际上明确地指定了行为–信号直接发送到执行写操作的线程。但是,Linux是否遵守还不清楚,Linux文档在这里没有帮助。对Linux行为的检查表明它符合POSIX,但没有证明这一点,对源代码的阅读为我们提供了有关当前版本Linux的必要证明。

tl; dr:它总是由进行写操作的线程处理。

POSIX标准

POSIX标准规定(自IEEE Std。1003.1-2001 / Cor 2-2004起),SIGPIPE是由于在没有读取器的情况下写入管道而产生的,因此必须将其传递给执行写入的线程。参见EPIPE in the ERRORS section of the description of write()(强调我的观点):

[EPIPE]尝试写入未打开以供任何进程读取的管道或FIFO,或仅一端开放的管道或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

为该过程生成的信号应准确地传递到该过程中[…]尚未阻止信号传递的那些线程之一。如果[…]过程中的所有线程都阻止了信号的传递,则该信号应在该过程中保持待处理状态,直到[…]一个线程取消阻止信号的传递,或者将与该信号相关的操作设置为忽略该信号。 / p>

我的实验表明,在具有最新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如何发送?它源自pipe_write()中的fs/pipe.c,后者在send_sig()上调用task_struct current。使用current已经暗示信号是线程导向的,但让我们继续。 send_sig()函数是在kernel/signal.c中定义的,并且通过某种间接调用,最终用__send_signal()调用pid_type type = PIDTYPE_PID

在Linux术语中,PID refers to a single thread。毫无疑问,使用这些参数,待处理信号列表是特定于线程的,而不是共享的。并且complete_signal()(在函数末尾调用)甚至没有尝试找到要唤醒的线程,它只是返回,因为已经选择了该线程。我不完全了解信号队列的工作方式,但似乎该队列是按线程的,因此当前线程是获取信号的线程。