在按 ENTER 之前捕获 SIGINT 后程序不会结束?
Program won't end after catching SIGINT before pressing ENTER?
为什么我的程序在终端中按下 Ctrl+C[=26 后直到按下 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 键。这是为什么?
这是因为 fgetc()
正在阻止执行,并且您选择处理 SIGINT 的方式 - fgetc()
不会被 EINTR 中断(请参阅@AnttiHaapala 的回答以获取更多解释)。因此只有在您按下 enter 后,释放 fgetc()
,keepRunning 才会被评估。
终端也有缓冲,所以只有当你按下回车键时,它才会将字符发送到 FILE *
缓冲区,并由 fgetc()
一个接一个地读取。这就是为什么它只在按下回车后才存在,而不是其他键。
"solve" 的几个选项之一是使用非阻塞标准输入、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;
}
}
}
fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) & ~O_NONBLOCK);
exit(EXIT_SUCCESS);
}
另请注意,我没有使用 fgetc()
。由于 FILE *
的缓冲特性,它不适用于非阻塞 IO。
以上程序仅用于教育目的,不得 "production" 使用。有几个问题需要注意,例如:
- 所有 libc / 系统调用都需要进行错误测试。
- 如果输出比输入慢(
printf()
可能很容易慢),可能会导致饥饿,信号不会被捕获(内循环只有在输入over/slower后才会退出) .
- 系统调用的性能/减少:
read()
可以填充更大的缓冲区。
epoll_wait
可以 return 多个事件而不是 1 个。
通常系统调用 return 和 errno == EINTR
如果在它们阻塞时传递了信号,这将导致 fgetc
提前到 return 并出现错误情况Control-C 被击中后。问题是 signal
设置的信号将被设置为自动重启模式,即底层 read
系统调用将在信号处理程序完成后立即重新启动。
正确的解决方法是删除自动重启,但它确实使正确使用稍微有些棘手。在这里,我们查看 return 值是否是 fgetc
中的 EOF
,然后它是否是由 EINTR
引起的,如果布尔值不正确,则重新启动循环。
struct sigaction action = {
.sa_flags = 0,
.sa_handler = intHandler
};
sigaction(SIGINT, &action, NULL);
int ch;
while (1) {
ch = fgetc(stdin);
if (ch == EOF) {
if (errno == EINTR) {
if (keepRunning) {
continue;
}
break;
}
break;
}
}
为什么我的程序在终端中按下 Ctrl+C[=26 后直到按下 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 键。这是为什么?
这是因为 fgetc()
正在阻止执行,并且您选择处理 SIGINT 的方式 - fgetc()
不会被 EINTR 中断(请参阅@AnttiHaapala 的回答以获取更多解释)。因此只有在您按下 enter 后,释放 fgetc()
,keepRunning 才会被评估。
终端也有缓冲,所以只有当你按下回车键时,它才会将字符发送到 FILE *
缓冲区,并由 fgetc()
一个接一个地读取。这就是为什么它只在按下回车后才存在,而不是其他键。
"solve" 的几个选项之一是使用非阻塞标准输入、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;
}
}
}
fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) & ~O_NONBLOCK);
exit(EXIT_SUCCESS);
}
另请注意,我没有使用 fgetc()
。由于 FILE *
的缓冲特性,它不适用于非阻塞 IO。
以上程序仅用于教育目的,不得 "production" 使用。有几个问题需要注意,例如:
- 所有 libc / 系统调用都需要进行错误测试。
- 如果输出比输入慢(
printf()
可能很容易慢),可能会导致饥饿,信号不会被捕获(内循环只有在输入over/slower后才会退出) . - 系统调用的性能/减少:
read()
可以填充更大的缓冲区。epoll_wait
可以 return 多个事件而不是 1 个。
通常系统调用 return 和 errno == EINTR
如果在它们阻塞时传递了信号,这将导致 fgetc
提前到 return 并出现错误情况Control-C 被击中后。问题是 signal
设置的信号将被设置为自动重启模式,即底层 read
系统调用将在信号处理程序完成后立即重新启动。
正确的解决方法是删除自动重启,但它确实使正确使用稍微有些棘手。在这里,我们查看 return 值是否是 fgetc
中的 EOF
,然后它是否是由 EINTR
引起的,如果布尔值不正确,则重新启动循环。
struct sigaction action = {
.sa_flags = 0,
.sa_handler = intHandler
};
sigaction(SIGINT, &action, NULL);
int ch;
while (1) {
ch = fgetc(stdin);
if (ch == EOF) {
if (errno == EINTR) {
if (keepRunning) {
continue;
}
break;
}
break;
}
}