为什么alarm()会导致fgets()停止等待?

8

我正在尝试使用 C 语言中的信号。我的主函数基本上使用 fgets(name, 30, stdin) 请求一些输入,然后等待。我使用 alarm(3) 设置了一个闹钟,并重新分配了 SIGALRM 调用一个名为 myalarm 的函数,该函数只是调用 system("say PAY ATTENTION")。但是在闹钟响起后,fgets() 停止等待输入,而我的主函数继续执行。即使我将 myalarm 更改为仅设置某个变量并不对其进行任何操作,这种情况仍会发生。

void myalarm(int sig) {
    //system("say PAY ATTENTION");
    int x = 0;
}

int catch_signal(int sig, void (*handler)(int)) { // when a signal comes in, "catch" it and "handle it" in the way you want
    struct sigaction action;  // create a new sigaction
    action.sa_handler = handler;  // set it's sa_handler attribute to the function specified in the header
    sigemptyset(&action.sa_mask);  // "turn all the signals in the sa_mask off?" "set the sa_mask to contian no signals, i.e. nothing is masked?"
    action.sa_flags = 0; // not sure, looks like we aren't using any of the available flags, whatever they may be

    return sigaction(sig, &action, NULL);  // here is where you actually reassign- now when sig is received, it'll do what action tells it
}

int main() {

    if(catch_signal(SIGINT, diediedie)== -1) {
        fprintf(stderr, "Can't map the SIGINT handler");
        exit(2);
    }

    if(catch_signal(SIGALRM, myalarm) == -1) {
        fprintf(stderr, "Can't map the SIGALAM handler\n");
        exit(2);
    }

    alarm(3);

    char name[30];
    printf("Enter your name: ");
    fgets(name, 30, stdin);

    printf("Hello, %s\n", name);

    return 0;

}

为什么alarm()会让fgets()停止等待输入?

编辑:添加了我的catch_signal函数的代码,并根据评论中的建议改用sigaction而非signal,但问题仍然存在。


5
请参考 man 7 signal,子标题为“通过信号处理程序中断系统调用和库函数”的部分。 - Retr0id
我看到了这样的内容:“如果对以下接口之一的阻塞调用被信号处理程序中断,则在使用了SA_RESTART标志的情况下,当信号处理程序返回后,调用将自动重新启动;否则,调用将失败并出现EINTR错误…”而fgets()没有列为可以重新启动的函数之一。那么我想做的事情是不可能的吗? - Aaron Parisi
fgets() 在底层使用 read 系统调用。顺便说一下,在我的系统上(Arch Linux x86_64),它不会被中断。 - Retr0id
@Retr0spectrum:你的 C 代码实现可以检测到 EINTR 并自动重新启动读取。 - jxh
你可以将fgets()放在一个循环中,以便在errno等于EINTR时重试它。例如:do{ fgets(name, 30, stdin); }while ( errno == EINTR); - TonyB
2
你没有展示 catch_signal(),但我猜测你使用的是 signal() 而不是 sigaction(),这意味着由 fgets() 调用的 read() 没有被重新启动。使用 sigaction() 来控制它。 - Jonathan Leffler
2个回答

4
答案很可能是与操作系统/系统相关的。fgets()函数通常会进行系统调用,如read()。如果检测到信号,系统调用可能会终止。在这个问题的情况下,fgets()函数已经进行了系统调用(可能是read()系统调用)来从stdin读取一个字符。SIGALRM导致系统调用终止,并将errno设置为EINTR。这也导致fgets()函数终止,而没有读取任何字符。
这并不是不寻常的,这只是操作系统实现信号的方式。
为了避免这个问题,我经常像这样在一个循环中包装fgets()函数:
do {
   errno=0;
   fgets(name, 30, stdin);
   } while(EINTR == errno);

这将需要你:

#include <stdio.h>(正如TonyB所建议的那样)。


我会在“fgets()函数经常进行系统调用”后面加上[citation needed] ;-) - spectras
2
这里的循环有问题。标准输入输出函数不可像那样重新启动;在错误之后缓冲区内容和文件位置是未指定的。如果你使用了标准输入输出函数并且不希望信号成为致命错误,必须使用 sigactionSA_RESTART - R.. GitHub STOP HELPING ICE

3
关于为什么警报信号会中断读取的问题,有两个原因:
  1. 这是Unix过去使用的方式,并且因为在操作系统中实现更容易而被采用。(一方面听起来有点不够好,但"不要担心难事"的态度在Unix的成功中起到了一定作用。这是Richard P. Gabriel的史诗般的《Worse Is Better》文章的主题)。
  2. 如果需要的话,它可以轻松实现一个超时并放弃读取的读取操作(请参见我对此问题的其他回答)。
但是,正如其他评论和答案所讨论的那样,中断行为有些过时;现代大多数系统(至少是Unix和Linux)现在会自动重新启动中断的系统调用,如read,基本上像您希望的那样。(另外,正如其他地方指出的那样,如果您知道自己在做什么,您可能能够在两种行为之间进行选择。)

但最终,这是一个灰色地带; 我相当肯定C标准将其定义为未指定、实现定义或未定义的情况,如果您使用alarm或其他信号中断系统调用会发生什么。


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