在按ENTER键之前捕获SIGINT后,程序不会结束吗?

为什么在按 Ctrl + C 后在终端中按 ENTER 之前,程序不会结束?

这是我的代码:

static volatile sig_atomic_t keepRunning = 1;

void intHandler(int sig) 
{
    keepRunning = 0;
}

int main(int argc,char *argv[])
{
    signal(SIGINT,intHandler);

    int ch; 
    while((ch = fgetc(stdin)) && keepRunning)
    {
      ...
    }
    exit(EXIT_SUCCESS);
}

我已经设置了while循环以从stdin中读取字符并运行直到捕获SIGINT为止。之后,keepRunning将被设置为0,循环应结束并终止程序。但是,当我按 Ctrl + C 时,我的程序不再接受任何输入,但是直到我按 ENTER 键。这是为什么?

rxwywy 回答:在按ENTER键之前捕获SIGINT后,程序不会结束吗?

这是因为fgetc()阻止了执行,并且您选择处理SIGINT的方式-fgetc()不会因EINTR而中断(有关更多说明,请参见@AnttiHaapala的回答)。因此,只有在按下Enter键(释放fgetc())之后,keepRunning才会被评估。

终端也被缓冲,因此只有在按下回车键时,才会将字符发送到FILE *缓冲区,并由fgetc()逐一读取。这就是为什么它仅在按Enter键而不是其他键之后才存在的原因。

“解决”它的几种方法之一是使用非阻塞stdin,signalfd和epoll(如果使用linux):

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/epoll.h>
#include <sys/signalfd.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <error.h>

int main(int argc,char *argv[])
{
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask,SIGINT);

    /* Block signals so that they aren't handled
       according to their default dispositions */
    sigprocmask(SIG_BLOCK,&mask,NULL); // need check

    // let's treat signal as fd,so we could add to epoll
    int sfd = signalfd(-1,0); // need check

    int epfd = epoll_create(1); // need check

    // add signal to epoll
    struct epoll_event ev = { .events = EPOLLIN,.data.fd = sfd };
    epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&ev); // need check

    // Make STDIN non-blocking 
    fcntl(STDIN_FILENO,F_SETFL,fcntl(STDIN_FILENO,F_GETFL) | O_NONBLOCK);

    // add STDIN to epoll
    ev.data.fd = STDIN_FILENO;
    epoll_ctl(epfd,STDIN_FILENO,&ev); // need check

    char ch;
    int keepRunning = 1; // no need to synchronize anymore
    while(keepRunning) {
        epoll_wait(epfd,&ev,1,-1); // need check,must be always 1
        if (ev.data.fd == sfd) {
            printf("signal caught\n");
            keepRunning = 0;
        } else {
            ssize_t r;
            while(r = read(STDIN_FILENO,&ch,1) > 0) {
                printf("%c",ch);
            }
            if (r == 0 && errno == 0) { 
                /* non-blocking non-eof will return 0 AND EAGAIN errno */
                printf("EOF reached\n");
                keepRunning = 0;
            } else if (errno != EAGAIN) {
                perror("read");
                keepRunning = 0;
            }
        }
    }
    exit(EXIT_SUCCESS);
}

还要注意,我没有使用fgetc()。由于FILE *的缓冲特性,它不能与非阻塞IO一起很好地工作。

以上程序仅用于教育目的,不用于“生产”用途。有几个问题需要注意,例如:

  • 所有libc /系统调用都需要进行错误测试。
  • 如果输出的速度慢于输入的速度(printf()可能很慢),则可能会导致饥饿并且信号不会被捕获(仅在输入速度过慢/过慢时,内循环才会退出)。
  • 性能/减少系统调用:
    • read()可以填充更大的缓冲区。
    • epoll_wait可以返回多个事件,而不是1。
,

通常,如果在阻塞时传递了信号,系统调用将以errno == EINTR返回,这将导致fgetc在按下Control-C时以错误状态提前返回。问题是由signal设置的信号将设置为自动重启模式,即,底层的read系统调用将在信号处理程序完成后立即重启。

正确的解决方法是删除自动重新启动,但是使用正确的重新启动确实有些棘手。在这里,我们查看返回值是否为EOF的{​​{1}},然后看是否是由fgetc引起的,如果布尔值不是true,则重新开始循环。

EINTR
本文链接:https://www.f2er.com/3091732.html

大家都在问