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

Why does alarm() cause fgets() to stop waiting?

我正在用 C 语言处理信号。我的主要功能基本上使用 fgets(name, 30, stdin) 请求一些输入,然后坐在那里等待。我用 alarm(3) 设置了一个警报,我重新分配 SIGALRM 来调用一个函数 myalarm,它只调用 system("say PAY ATTENTION")。但是警报响起后,fgets() 停止等待输入,我的主 fn 继续。即使我将 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,但问题仍然存在。

答案很可能是 OS/system 具体的。

(如Retr0spectrum所述) fgets()函数经常进行系统调用,例如read()。如果检测到信号,系统调用可以终止。在这个问题的例子中,fgets() 函数进行了系统调用(可能是 read() 系统调用)以从标准输入读取一个字符。 SIGALRM 导致系统调用终止,并将 errno 设置为 EINTR。这也会导致 fgets() 函数终止,而不读取任何字符。

这并不罕见。这就是 OS 实现信号的方式。

为了避免这个问题,我经常将 fgets() 函数包装在一个循环中,如下所示:

do {
   errno=0;
   fgets(name, 30, stdin);
   } while(EINTR == errno);

它将要求您:#include <stdio.h>

(根据 TonyB 的建议)。

关于为什么报警信号中断读取的问题,有两个原因:

  1. 这是 Unix 过去的做法,因为它在 OS 中更容易实现。 (一方面,这听起来有点蹩脚,但 "don't sweat the hard stuff" 态度首先是 Unix 成功的部分原因。这是 Richard P. Gabriel 的史诗 Worse Is 的主题更好论文。)

  2. 它可以轻松实现超时并放弃的读取,如果这是您想要的。 (参见我对 this other question 的回答。)

但是,正如其他评论和答案所讨论的那样,中断行为有些过时了;大多数现代系统(Unix 和 Linux,至少)现在自动重启中断的系统调用,例如 read,或多或少如您所愿。 (也正如其他地方指出的那样,如果您知道自己在做什么,您也许可以 select 在两种行为之间。)

不过,归根结底,这是一个灰色地带;我很确定 C 标准未指定或 implementation-defined 或未定义如果您用警报或其他信号中断系统调用会发生什么。