为啥 alarm() 导致 fgets() 停止等待?

Posted

技术标签:

【中文标题】为啥 alarm() 导致 fgets() 停止等待?【英文标题】:Why does alarm() cause fgets() to stop waiting?为什么 alarm() 导致 fgets() 停止等待? 【发布时间】:2017-09-10 22:45:19 【问题描述】:

我正在使用 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 函数添加了代码,并且根据其中一个 cmets,使用了 sigaction 而不是 signal,但问题仍然存在。

【问题讨论】:

参见man 7 signal,副标题“信号处理程序中断系统调用和库函数” 我看到这个:“如果对以下接口之一的阻塞调用被信号处理程序中断,那么如果使用了 SA_RESTART 标志,则在信号处理程序返回后,调用将自动重新启动; 否则调用将失败并出现错误 EINTR..." 并且 fgets() 未列为可以重新启动的函数之一。我试图做的事情是不可能的吗? fgets() 在后台使用 read 系统调用。顺便说一句,在我的系统上它没有被中断(Arch Linux x86_64) @Retr0spectrum:您的 C 实现可能会检测到 EINTR 并为您重新启动读取。 你没有显示catch_signal(),但我猜你使用signal()而不是sigaction(),这意味着fgets()调用的read()没有重新启动。使用sigaction() 来控制它。 【参考方案1】:

答案很可能是特定于操作系统/系统的。

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

这并不罕见。这就是操作系统实现信号的方式。

为了避免这个问题,我经常会像这样将 fgets() 函数包装在一个循环中:

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

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

(按照 TonyB 的建议)。

【讨论】:

我会在 “fgets() 函数经常进行系统调用” ;-) 之后添加一个 [需要引用] 这里的循环是错误的。 Stdio 函数不能像那样重新启动;错误后未指定缓冲区内容和文件位置。如果您正在使用 stdio 并且不希望信号成为致命错误,您必须sigactionSA_RESTART 结合使用。【参考方案2】:

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

    这是 Unix 过去的做法,因为它更容易在操作系统中实现。 (一方面这听起来有点蹩脚,但“不要为难的东西出汗”的态度首先是 Unix 成功的部分原因。这是 Richard P. Gabriel 史诗 的主题越差越好文章。)

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

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

不过,最终还是一个灰色地带;我很确定 C 标准未指定或实现定义或未定义如果您使用警报或其他信号中断系统调用会发生什么。

【讨论】:

以上是关于为啥 alarm() 导致 fgets() 停止等待?的主要内容,如果未能解决你的问题,请参考以下文章

为啥这个 fgets 函数会给我一个分段错误?

为啥要使用 strtok(line, "\n") **not** 来去除 fgets() 留下的换行符

Android:更改视图顺序会导致“不幸的是,应用程序已停止”OK。为啥?

开雷电模拟器的时候virtualbox headless fronted停止运行为啥

signal.alarm() 处理程序导致 pyserial 出现问题

fgets与fputs