XOPEN_SOURCE 和信号处理

8
在下面的程序中,如果我取消注释_XOPEN_SOURCE行,当我按下C-c时,我的程序会终止,如果我不注释那一行,同样的程序不会终止。有人知道_XOPEN_SOURCE以何种方式影响信号处理吗?我使用gcc(4.6.3)和glibc(2.15)在Linux上运行。
/* #define _XOPEN_SOURCE 700 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

typedef void (*sighandler_t)(int);

void handle_signal(int signo)
{
    printf("\n[MY_SHELL] ");
    fflush(stdout);
}

int main()
{
    int c;
    signal(SIGINT, SIG_IGN);
    signal(SIGINT, handle_signal);
    printf("[MY_SHELL] ");
    while ((c = getchar()) != EOF) {
        if (c == '\n')
            printf("[MY_SHELL] ");
    }
    printf("\n");
    return 0;
}

顺便提一下,char c:不行,不行,不行,不行,不行。EOF需要是带外信号。这就是为什么getchar返回一个int的原因。 - Dave
@Dave 你说得对。这实际上是我在网上找到的一个例子。我没有注意到它。现在正在修复。 - yasar
2个回答

4
问题在于,在安装信号处理函数时,signal()函数可以具有两种不同的行为形式:
  • System V语义,其中信号处理程序是“一次性”的——也就是说,在调用信号处理函数后,信号的处理方式将重置为SIG_DFL,被信号中断的系统调用不会重新启动;或者
  • BSD语义,其中信号处理程序在信号触发时不会重置,信号在信号处理程序执行时被阻塞,大多数被中断的系统调用将自动重新启动。
在Linux上使用glibc时,如果定义了_BSD_SOURCE,则会获得BSD语义,否则将获得System V语义。_BSD_SOURCE宏默认已定义,但是如果您定义了_XOPEN_SOURCE(或一些其他宏,例如_POSIX_SOURCE_SVID_SOURCE),则会取消默认定义。
在System V语义下,如果读取getchar()底层的read()系统调用被SIGINT中断,则getchar()将返回EOF,并将errno设置为EINTR(这会导致您的程序正常退出)。此外,在第一个SIGINT之后,该信号的处理方式将重置为默认值,并且SIGINT的默认操作是终止进程(因此,即使您的程序在第一个SIGINT中幸存下来,第二个也会导致它异常退出)。
解决方案是根本不使用signal()安装信号处理函数;而是应该使用可移植的sigaction(),它在任何地方都提供相同的语义。将sa_flags设置为SA_RESTARTsigaction()将提供BSD语义,这正是您想要的。

但是我应该注意,在任何情况下,当第一次按下C-c时(处理程序已经设置),程序都不会终止。 - Alex Bakulin
这里发生的不是那样的情况。 _XOPEN_SOURCE 700 语义缺少 SA_RESTART,导致 EINTR 并在 getchar 中返回 EOF,从而退出程序。请参见我的答案。 - Dave
@Dave:你说得很对,我已经更新了这个答案,并加入了系统调用重新启动的信息。 - caf

1
这些微妙的行为差异是为什么通常更喜欢使用sigprocmask()而不是signal()。定义了_XOPEN_SOURCE 700的版本调用(如strace所示)。
rt_sigaction(SIGINT, {SIG_IGN, [], SA_INTERRUPT|SA_NODEFER|SA_RESETHAND}, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGINT, {0x80484dc, [], SA_INTERRUPT|SA_NODEFER|SA_RESETHAND}, {SIG_IGN, [], SA_INTERRUPT|SA_NODEFER|SA_RESETHAND}, 8) = 0

而被注释掉的那个调用:

rt_sigaction(SIGINT, {SIG_IGN, [INT], SA_RESTART}, {SIG_DFL, [], 0}, 8) = 0 
rt_sigaction(SIGINT, {0x80484dc, [INT], SA_RESTART}, {SIG_IGN, [INT], SA_RESTART}, 8) = 0
< p > _XOPEN_SOURCE 缺少的重要额外标志是 SA_RESTART ,它允许 read 系统调用继续进行,就像没有发生信号一样。如果没有它,系统调用会指示失败, getchar() 返回 -1 (但指示失败,而不是真正的 EOF),您的程序将终止。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接