SIGPIPE为什么存在?


92

根据我的理解,SIGPIPE只能作为a的结果出现write(),它可以(并且确实)返回-1并设置errnoEPIPE...那么,为什么我们会有信号的额外开销?每次使用管道时,我都会忽略SIGPIPE并且从未感到任何痛苦,这是我缺少的东西吗?

Answers:


111

我不买以前接受的答案。SIGPIPE确切地是在write失败时生成的EPIPE,而不是事先生成的-实际上,避免SIGPIPE不更改全局信号配置的一种安全方法是使用临时屏蔽它pthread_sigmask,执行write,然后执行sigtimedwait(超时为零)以消耗任何待处理的SIGPIPE信号(发送到调用线程,而不是进程),然后再次对其进行屏蔽。

我相信SIGPIPE存在的原因要简单得多:为纯“过滤器”程序建立合理的默认行为,该程序可以连续读取输入,以某种方式对其进行转换并写入输出。如果没有SIGPIPE,则除非这些程序显式处理写错误并立即退出(总之,这不是所有写错误的理想行为),否则它们将继续运行,直到它们的输入用尽,即使它们的输出管道已关闭。当然,您可以SIGPIPE通过显式检查EPIPE并退出来复制行为,但是的全部目的SIGPIPE是在程序员懒惰时默认实现此行为。


15
+1。提示是SIGPIPE默认会杀死您-它并非旨在中断系统调用,而是旨在终止您的程序!如果您能够在信号处理程序中处理信号,则同样可以处理的返回码write
尼古拉斯·威尔逊

2
您是对的,我不知道为什么我一开始就接受。这个答案是有道理的,尽管IMO很奇怪,例如在Linux上,这种懒惰是由内核而不是libc实现的。
Shea Levy

5
听起来这个答案基本上可以归结为:“因为我们没有例外”。但是,人们忽略C语言中的返回码比不仅仅是write()调用要广泛得多。是什么使写入如此特别以至于需要自己的信号?也许纯过滤器程序比我想象的要普遍得多。
Arvid

@Arvid SIGPIPE是Unix人发明的,用于解决他们在极其常见的过滤器程序环境中遇到的问题。我们要做的就是阅读启动系统的启动脚本。
哈兹

@SheaLevy哪些Unix系统完全在其libc中实现SIGPIPE?
哈兹

23

因为您的程序可能正在等待I / O或被挂起。SIGPIPE异步中断程序,终止系统调用,因此可以立即处理。

更新资料

考虑一条管道A | B | C

仅出于确定性考虑,我们假定B是规范的复制循环:

while((sz = read(STDIN,bufr,BUFSIZE))>=0)
    write(STDOUT,bufr,sz);

Bread(2)调用中被阻止,以等待终止A时的数据C。如果您等待write(2)的返回代码,B何时会看到它?答案当然不是直到A写更多的数据(这可能是一个漫长的等待-如果A被其他东西阻塞了怎么办?)。顺便说一下,请注意,这也使我们可以使用一个更简单,更简洁的程序。如果您依赖于写入的错误代码,则需要类似以下内容:

while((sz = read(STDIN,bufr,BUFSIZE))>=0)
    if(write(STDOUT,bufr,sz)<0)
        break;

另一个更新

啊哈,您对写入行为感到困惑。您会看到,当关闭带有挂起写操作的文件描述符时,SIGPIPE就在此时发生。尽管写入最终将返回-1 ,但信号的全部目的是异步通知您不再可能进行写入。这是使管道的整个优雅的协同例程结构在UNIX中工作的一部分。

现在,我可以在几本UNIX系统编程书籍中为您提供整个讨论,但是有一个更好的答案:您可以自己验证。编写一个简单的B程序[1]-您已经有了勇气,您所需要的只是一个main,其中有些包含-并为添加一个信号处理程序SIGPIPE。运行类似的管道

cat | B | more

然后在另一个终端窗口中,将调试器连接到B,然后在B信号处理程序中放置一个断点。

现在,杀死更多,B应该在信号处理程序中中断。检查堆栈。您会发现读取仍在进行中。让信号处理程序继续执行并返回,然后查看write返回的结果- 然后为-1。

[1]自然,您将用C编写B程序。


3
为什么B用SIGPIPE很快看到C的终止?B将在读取时保持阻塞,直到将某些内容写入其STDIN为止,此时它将调用write(),然后才引发SIGPIPE /返回-1。
Shea Levy

2
我非常喜欢这个答案:SIGPIPE让死亡立即从管道的输出端传播回去。否则,管道的N个元素中的每个元素都要花费一个复制程序周期才能杀死管道,并导致输入端生成N行,这些行永远不会到达末尾。
Yttrill 2012年

18
这个答案是不正确的。SIGPIPE不是在读期间发表,但在write。你并不需要写一个C程序进行测试,只需运行cat | head,并pkill head在一个单独的终端。您将看到cat快乐地等待它的出现- read()仅当您键入内容并按Enter时cat,它才会因管道破裂而死,正是因为它试图写入输出。
user4815162342

5
-1 SIGPIPE不能传送到Bwhile B被阻止,read因为SIGPIPE只有在B尝试时才会生成write。同时调用write时,没有线程可以“正在等待I / O或挂起” 。
Dan Moulding

3
您可以发布一个完整的程序,该程序显示SIGPIPE被阻止时正在上升read吗?我根本无法重现这种行为(并且实际上不确定为什么我首先要接受这种行为)
Shea Levy 2014年

7

https://www.gnu.org/software/libc/manual/html_mono/libc.html

该链接说:

必须同时在两端打开管道或FIFO。如果您从没有任何进程写入的管道或FIFO文件中读取(可能是因为它们已全部关闭文件或退出了),则读取将返回文件末尾。写入没有读取过程的管道或FIFO被视为错误条件;它会生成SIGPIPE信号,如果该信号被处理或阻塞,则会失败并显示错误代码EPIPE。

—宏:int SIGPIPE

管道破损。如果使用管道或FIFO,则必须设计应用程序,以便一个进程打开管道进行读取,然后另一个进程开始写入。如果读取过程从未开始或意外终止,则写入管道或FIFO会引发SIGPIPE信号。如果SIGPIPE被阻止,处理或忽略,则有问题的调用将失败,而是使用EPIPE。

管道和FIFO特殊文件在管道和FIFO中有更详细的讨论。


5

我认为这是为了使错误处理正确,而无需在写入管道的所有内容中花费大量代码。

一些程序忽略的返回值write(); 没有SIGPIPE它们,将无用地生成所有输出。

如果检查write()失败的返回值的程序失败,则会显示一条错误消息。这对于破裂的管道是不合适的,因为这对整个管道来说并不是真正的错误。


2

机器信息:

Linux 3.2.0-53-generic#81-Ubuntu SMP Thu Aug 22 21:01:03 UTC 2013 x86_64 x86_64 x86_64 GNU / Linux

gcc版本4.6.3(Ubuntu / Linaro 4.6.3-1ubuntu5)

我在下面编写了这段代码:

// Writes characters to stdout in an infinite loop, also counts 
// the number of characters generated and prints them in sighandler
// writestdout.c

# include <unistd.h>
# include <stdio.h>
# include <signal.h>
# include <string.h>

int writeCount = 0;    
void sighandler(int sig) {
    char buf1[30] ;
    sprintf(buf1,"signal %d writeCount %d\n", sig, writeCount);
    ssize_t leng = strlen(buf1);
    write(2, buf1, leng);
    _exit(1);

}

int main() {

    int i = 0;
    char buf[2] = "a";

    struct sigaction ss;
    ss.sa_handler = sighandler;

    sigaction(13, &ss, NULL);

    while(1) {

        /* if (writeCount == 4) {

            write(2, "4th char\n", 10);

        } */

        ssize_t c = write(1, buf, 1);
        writeCount++;

    }

}

// Reads only 3 characters from stdin and exits
// readstdin.c

# include <unistd.h>
# include <stdio.h>

int main() {

    ssize_t n ;        
    char a[5];        
    n = read(0, a, 3);
    printf("read %zd bytes\n", n);
    return(0);

}

输出:

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 11486

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 429

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 281

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 490

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 433

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 318

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 468

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 11866

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 496

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 284

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 271

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 416

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 11268

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 427

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 8812

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 394

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 10937

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 10931

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 3554

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 499

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 283

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 11133

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 451

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 493

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 233

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 11397

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 492

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 547

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 441

您可以看到,在每种情况下SIGPIPE,只有在写入过程写入了3个以上的字符后,才会收到该字符。

这是否不能证明SIGPIPE在读取过程终止后立即尝试将更多数据写入封闭的管道后不会立即生成?

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.