【问题标题】:Why was default SIGPIPE handler changed?为什么更改了默认 SIGPIPE 处理程序?
【发布时间】:2018-12-16 12:02:08
【问题描述】:

我正在进行一项测试,有人问我如何让“读取”睡眠或“写入”停止进程”

对于后者,我不明白为什么我的 sigpipe 确实被提升了,但并没有停止这个过程:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#define READING 0
#define WRITING 1
#define DESCRIPTOR_COUNT 2

void signal_handler(int signal){
    printf("sigpipe received\n");
}

int main(void)
{
    int tube[DESCRIPTOR_COUNT];
    pipe(tube);
//    signal(SIGPIPE, signal_handler);
    close(tube[READING]);

    if(write(tube[WRITING], "message", 8)<0)
        if(errno==EPIPE)
            printf("EPIPE returned\n");

    printf("123");

    return EXIT_SUCCESS;
}

没有 signal()(引用)

使用 signal()(未引用)

SIGPIPE 确实收到了,但如果我不处理它,进程应该停止,但由于我可以写“123”,这意味着进程没有停止。 为什么?

我也在 Fedora 28 上,我正在使用代码块 17.12。

SIGPIPE 是否被忽略...?原因?

解决方案?

struct sigaction action;
action.sa_handler = SIG_DFL;
sigaction(SIGPIPE, &action, 0);

用它替换signal() 将具有预期的默认行为!

编辑我现在已将标题从“SIGPIPE 不停止进程”更改为“为什么更改了默认 SIGPIPE 处理程序?”

================================================ =======

回答

在与来自 codeblocks 的人交谈后,codeblocks 使用 wxWidgets,并且在 linux(此处为 fedora 28)上 wxWidgets 使用 gtk 库,正如 cmets 中的 Mark Plotnick 所解释的,gtk 更改了 SIGPIPE 的信号处理程序,因为 codeblocks 运行代码使用fork 或 exec,通过代码块运行的代码受 gtk 库的影响。

【问题讨论】:

  • 嗯,适用于 me,Linux。平台是什么?它适用于这个minimal reproducible example 吗?如果忽略 SIGPIPE,write 将返回 EPIPE。是这样吗?
  • Fedora 28,epipe 没有返回:/
  • write()==EPIPE ... :: write 出错时返回 -1,并将 errno 设置为 EPIPE。
  • 从命令行尝试一下。
  • 在 gdb 下运行代码块。原来 gtk 库在几个地方正在使进程忽略 sigpipe。第一个是 github.com/linuxmint/gtk/blob/master/gtk/gtkmain.c#L689 。评论说 Since 2.18, GTK+ calls signal (SIGPIPE, SIG_IGN) during initialization, to ignore SIGPIPE signals, since these are almost never wanted in graphical applications. If you do need to handle SIGPIPE for some reason, reset the handler after gtk_init(), but notice that other libraries (e.g. libdbus or gvfs) might do similar things. 可能想向代码块人员提出功能请求。

标签: c pipe signals codeblocks


【解决方案1】:

您报告的行为与 Code::Blocks IDE 设置(隐式或显式)一致,SIGPIPE 行为与 SIG_IGN 一致。这是很容易继承的。这不是我所期望的——我希望您的程序在 SIGPIPE(以及实际上所有其他信号)设置为 SIG_DFL(默认信号行为)的情况下启动。如果这被证明是问题所在,那么您就有了向 Code::Blocks 的开发人员报告错误的依据。如果事实证明这不是问题所在,那么我们需要认真思考一下到底发生了什么*

您可以通过注意来自signal() 的返回值,或通过使用sigaction() 来询问信号处理模式而不修改它来证明这是否是发生在 Code::Blocks 中。

例如:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#define READING 0
#define WRITING 1
#define DESCRIPTOR_COUNT 2

static void signal_handler(int signum)
{
    // Lazy; normally, I'd format the signal number into the string, carefully
    (void)signum;
    write(STDOUT_FILENO, "sigpipe received\n", sizeof("sigpipe received\n")-1);
}

int main(void)
{
    void (*handler)(int) = signal(SIGPIPE, signal_handler);

    if (handler == SIG_DFL)
        printf("old handler was SIG_DFL\n");
    else if (handler == SIG_IGN)
    {
        printf("old handler was SIG_IGN\n");
        (void)signal(SIGPIPE, SIG_IGN);
    }
    else
    {
        // Standard C does not allow a cast from function pointer to object pointer
        //printf("there was a non-standard handler installed (%p)\n", (void *)handler);
        printf("there was a non-standard handler installed\n");
    }

    int tube[DESCRIPTOR_COUNT];
    pipe(tube);    
    close(tube[READING]);

    if (write(tube[WRITING], "message", 8) < 0)
    {
        if (errno == EPIPE)
            printf("EPIPE returned\n");
        else
            printf("errno = %d\n", errno);
    }

    printf("123\n");

    return EXIT_SUCCESS;
}

请注意,在程序中使用signal() 设置信号处理程序的标准习惯用法是:

if (signal(signum, SIG_IGN) != SIG_IGN)
    signal(signum, signal_handler);

这意味着如果一个程序受到信号保护,它就会保持受保护状态。如果它正在处理信号(默认情况下,或者可能通过之前对signal() 的调用明确),那么您安装自己的信号处理程序。使用sigaction() 的等效代码是:

struct sigaction sa;
if (sigaction(signum, 0, &sa) == 0 && sa.sa_handler != SIG_IGN)
{
    sa.sa_handler = signal_handler;
    sa.sa_flag &= ~SA_SIGINFO;
    sigaction(signum, sa, 0);
}

(这样做的一个优点:结构是通过调用sigaction() 来初始化的,因此无需摆弄掩码。对标志的调整可确保使用基本处理程序,而不是扩展处理程序。 )

当我将源代码 (pipe13.c) 编译成程序 pipe13 并运行它时,我得到:

$ make pipe13
gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes pipe13.c -o pipe13
$ pipe13
old handler was SIG_DFL
sigpipe received
EPIPE returned
123
$ (trap '' 13; pipe13)
old handler was SIG_IGN
EPIPE returned
123
$

此变体使用sigaction() 来询问信号处理:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#define READING 0
#define WRITING 1
#define DESCRIPTOR_COUNT 2

int main(void)
{
    struct sigaction sa;
    if (sigaction(SIGPIPE, 0, &sa) != 0)
        fprintf(stderr, "siagaction() failed\n");
    else if (sa.sa_handler == SIG_DFL)
        printf("old handler was SIG_DFL\n");
    else if (sa.sa_handler == SIG_IGN)
        printf("old handler was SIG_IGN\n");
    else
        printf("there was a non-standard handler installed\n");

    int tube[DESCRIPTOR_COUNT];
    pipe(tube);    
    close(tube[READING]);

    if (write(tube[WRITING], "message", 8) < 0)
    {
        if (errno == EPIPE)
            printf("EPIPE returned\n");
        else
            printf("errno = %d\n", errno);
    }

    printf("123\n");

    return EXIT_SUCCESS;
}

运行时(程序pipe83),我得到:

$ pipe83
old handler was SIG_DFL
$ echo $?
141
$ (trap '' 13; pipe83)
old handler was SIG_IGN
EPIPE returned
123
$

请注意,使用默认信号处理,程序在打印123 之前终止。 POSIX shell 通过将退出状态报告为128 + N 来编码“孩子死于信号N”; SIGPIPE 是13,所以141 表示shell 死于SIGPIPE 信号。 (是的,现代年轻人可能会写 (trap '' PIPE; pipe83) 并且它有效——当我学习 shell 编程时,还没有这些细节。)

概括代码以测试 Code::Blocks 是否将任何其他信号设置为默认处理之外的任何其他信号并不难。不过,如果您想适应机器上可用的信号,这可能有点繁琐。


*chat 中,我们确定该程序在运行 Fedora 28 的 VMware 映像中运行,该映像托管在 Windows 10 计算机上。正因为如此,有足够多的可能出现问题的地方,以至于不清楚问题是否一定在 Code::Blocks 中——根本不清楚问题的根源。但是,问题确实似乎是测试程序在从 Code::Blocks 运行时将 SIGPIPE 处理设置为 SIG_IGN 而不是 SIG_DFL 启动。

【讨论】:

  • Code::Blocks IDE setting, implicitly or explicitly, the SIGPIPE behaviour to SIG_IGN 为什么代码块会决定更改默认行为?即使它使用 gnu gcc 声明默认行为应该是终止进程?参考:gnu.org/software/libc/manual/html_node/…
  • 我不知道——我没有编写 Code::Blocks,也没有检查过代码。但是一个可能的原因是,例如,Code::Blocks 在运行编译器时不希望自己获得 SIGPIPE 信号,因此它忽略了强制write() 系统调用报告EPIPE 错误的信号。另一种可能性是 Code::Blocks 是在忽略 SIGPIPE 的情况下启动的,并且它永远不会改变该状态。请注意,我只声称“符合”——并提供了允许您(作为 OP)测试 Code::Blocks 是否确实在忽略 SIGPIPE 的情况下启动您的程序的代码。
  • 这不是我所期望的,但是很难检测到 Code::Blocks 是如何启动的,以及它在启动时具有哪些信号配置与它为自己设置的信号(并且不会为它的孩子)。这可能是 Code::Blocks 中的错误;它可能是您用来启动 Code::Blocks 的任何错误。我不自夸为什么它是这样的。我只是提供一种方法来帮助您检测正在发生的事情,这将为您所看到的内容提供解释。如果您真的想修复它,您将编写一个程序,将所有信号重置为 SIG_DFL,然后执行作为其参数给出的程序。
  • 感谢您的宝贵时间。但是我知道“sa”的存储大小是未知的
  • 那么您需要在第一次包含之前激活 POSIX 扩展(#define _XOPEN_SOURCE 700#define _POSIX_C_SOURCE 200809L。如果这不起作用,那么您可能无法在您的机器上使用 sigaction();请改用signal() 代码。
【解决方案2】:

Code::Blocks 不是编译器,只是一个 IDE。它(可能)运行GCC 编译器。 GCC 编译器对于信号处理并不重要。阅读signal(7)signal-safety(7) 了解更多信息(禁止在信号处理程序内调用printf,因为printf 不是异步信号安全,所以信号处理程序内的printf("sigpipe received\n"); 是@ 987654325@)。信号处理主要由Linux kernel(参见kernel.org 上的源代码)完成,其中一小部分由您的C 标准库处理,可能是GNU glibc

似乎代码块更改了 SIGPIPE 的默认信号处理程序

这不太可能(而且几乎可以肯定是错误的)。您可以在您的程序上使用strace(1) 来了解它正在执行的系统调用。

  printf("123");

您忘记了\n。请记住,stdout 通常是行缓冲的。或者你应该打电话给fflush(3)

当您从 Code::Blocks 内部运行程序时,它可以在没有终端的情况下运行。我强烈建议在terminal emulator 中运行您的程序(请参阅tty(4)pty(7))。当stdout 是或不是tty 时,允许C 标准库使用isatty(3) 之类的东西来表现不同(特别是缓冲的方式不同,请参阅setvbuf(3))。阅读tty demystifiedtermios(3)

您也可以尝试通过将程序的标准输入、标准输出、标准错误重定向到文件或管道来运行您的程序(可能使用|&amp; cat|&amp; lessbashzsh),或任何不是一个tty。

顺便说一句,Code::Blocksfree software。你可以研究它的源代码来了解它在做什么。

考虑使用stdbuf(1) 来运行您的程序,使用不同的缓冲操作。

【讨论】:

  • 正如你所说它使用 gcc 编译器,事情在 命令行 SIGPIPE 停止进程,“123”没有打印,在代码块中使用“编译和构建”给了我不同的行为,这意味着代码块正在做某事
  • 我确定我在命令行中编译的文件是我在代码块上使用的完全相同的文件,我什至“干净”并重新“构建”我的文件在代码块上,我已经在命令行中检查了我使用cat main.c 编译的文件,它是同一个。
  • 先生,我读过你,但问题的核心不是printf() 是否在正确的流上打印,指令printf()在指令之后 @ 987654355@ ,它甚至不应该被处理。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-20
  • 1970-01-01
  • 1970-01-01
  • 2012-04-13
  • 1970-01-01
  • 2011-08-12
相关资源
最近更新 更多