【发布时间】:2011-12-03 17:27:46
【问题描述】:
据我了解,SIGPIPE 只能作为 write() 的结果出现,它可以(并且确实)返回 -1 并将 errno 设置为 EPIPE... 那么为什么我们有额外的信号的开销?每次我使用管道时,我都会忽略SIGPIPE,因此从未感到任何痛苦,是我错过了什么吗?
【问题讨论】:
标签: posix
据我了解,SIGPIPE 只能作为 write() 的结果出现,它可以(并且确实)返回 -1 并将 errno 设置为 EPIPE... 那么为什么我们有额外的信号的开销?每次我使用管道时,我都会忽略SIGPIPE,因此从未感到任何痛苦,是我错过了什么吗?
【问题讨论】:
标签: posix
我不买以前接受的答案。 SIGPIPE 是在 write 以 EPIPE 失败时生成的,而不是事先生成的 - 事实上,在不更改全局信号配置的情况下避免 SIGPIPE 的一种安全方法是用 pthread_sigmask 临时屏蔽它,执行 write ,然后执行sigtimedwait(超时为零)以消耗任何待处理的SIGPIPE 信号(发送到调用线程,而不是进程),然后再次取消屏蔽。
我相信SIGPIPE 存在的原因要简单得多:为不断读取输入、以某种方式转换并写入输出的纯“过滤器”程序建立健全的默认行为。如果没有SIGPIPE,除非这些程序显式处理写入错误并立即退出(无论如何,这可能不是所有写入错误的预期行为),即使它们的输出管道已关闭,它们仍将继续运行直到输入用完。当然,您可以通过显式检查 EPIPE 并退出来复制 SIGPIPE 的行为,但 SIGPIPE 的全部目的是在程序员懒惰时默认实现此行为。
【讨论】:
write 的返回码。
因为您的程序可能正在等待 I/O 或以其他方式挂起。 SIGPIPE 会异步中断您的程序,终止系统调用,因此可以立即处理。
更新
考虑一个管道A | B | C。
为了明确起见,我们假设 B 是规范复制循环:
while((sz = read(STDIN,bufr,BUFSIZE))>=0)
write(STDOUT,bufr,sz);
当C 终止时,B 在等待来自A 的数据的read(2) 调用中被阻塞。如果等待 write(2) 的返回码,B 什么时候才能看到呢?答案当然是直到 A 写入更多数据(这可能需要很长时间——如果 A 被其他东西阻塞了怎么办?)。注意,顺便说一下,这也让我们的程序更简单、更干净。如果你依赖于 write 的错误代码,你需要这样的东西:
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 信号处理程序中放置一个断点。
现在,杀死 more 并且 B 应该在您的信号处理程序中中断。检查堆栈。您会发现 read 仍在等待中。让信号处理程序继续执行并返回,然后查看 write 返回的结果 -- 然后 为 -1。
[1] 当然,你会用 C 编写你的 B 程序。:-)
【讨论】:
SIGPIPE不是在读取期间传递,而是在 write 期间传递。您不需要编写 C 程序来测试它,只需在单独的终端中运行 cat | head 和 pkill head。你会看到cat 快乐地在它的read() 中等待——只有当你输入一些东西并按下回车键时,cat 才会因管道损坏而死,这正是因为它试图写入输出。
SIGPIPE 无法传送到 B,而 B 在 read 上被阻止,因为在 B 尝试 write 之前不会生成 SIGPIPE。在同时调用write 时,任何线程都不能“等待 I/O 或以其他方式挂起”。
SIGPIPE 在被read 阻止时被提升?我根本无法重现这种行为(实际上我不确定为什么我首先接受了这种行为)
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 中有更详细的讨论。
【讨论】:
我认为在写入管道的所有内容中不需要大量代码即可正确处理错误。
有些程序会忽略write()的返回值;如果没有SIGPIPE,它们将毫无用处地生成所有输出。
检查write() 的返回值的程序可能会在失败时打印错误消息;这对于损坏的管道是不合适的,因为它对于整个管道来说并不是真正的错误。
【讨论】:
机器信息:
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不是在读取过程终止后而是在尝试将更多数据写入封闭管道之后立即生成的吗?
【讨论】: