这是因为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