为什么在按 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 <时,它才允许我在终端中键入任何命令。 / kbd>键。这是为什么?
答案 0 :(得分:2)
这是因为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, &mask, 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, EPOLL_CTL_ADD, 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一起很好地工作。
以上程序仅用于教育目的,不用于“生产”用途。有几个问题需要注意,例如:
printf()
可能很慢),则可能会导致饥饿并且信号不会被捕获(仅在输入速度过慢/过慢时,内循环才会退出)。read()
可以填充更大的缓冲区。epoll_wait
可以返回多个事件,而不是1。答案 1 :(得分:0)
通常,如果在阻塞时传递了信号,系统调用将以errno == EINTR
返回,这将导致fgetc
在按下Control-C时以错误状态提前返回。问题是由signal
设置的信号将设置为自动重启模式,即,底层的read
系统调用将在信号处理程序完成后立即重启。
正确的解决方法是删除自动重新启动,但是使用正确的重新启动确实有些棘手。在这里,我们查看返回值是否为EOF
的{{1}},然后看是否是由fgetc
引起的,如果布尔值不是true,则重新开始循环。
EINTR