如何使 libreadline 或 libedit 应对 SIGALRM?
How to make libreadline or libedit cope with SIGALRM?
我正在编写一个 Linux 与一些自定义硬件交互的程序
通过 GPIO 线。它使用一个计时器定期传送 SIGALRM
为了管理这些交互的时间,并且有相当
在信号处理程序中执行的一些工作。该计划提供了一个
命令行界面,我很想有行编辑
libreadline 提供的便利。不幸的是,它似乎
libreadline 不能很好地处理那些周期性信号。我也试过
libedit (a.k.a. “editline”) 的 readline 兼容模式,与
运气不好。
有没有一种简单的方法可以在程序中具有 readline 功能
收到大量 SIGALRM
信号?
症状
使用libreadline时,如果定时器是运行,一些字符
输入的内容随机回显两次。例如,键入“help”可能会导致
在终端上显示的“帮助”中。问题只是显而易见的
在回声中:程序确实收到了键入的单词(即“帮助”)。
在readline兼容模式下使用libedit时,如果定时器是
运行, readline()
returns NULL
每当被
SIGALRM
信号。
当计时器停止时,一切都按预期工作,两者都
libreadline 和 libedit。
环境
Ubuntu 20.04 带有最新的 apt 包 libreadline-dev(版本
8.0-4) 和 libedit-dev (3.1-20191231-1)。该程序最终将
部署在 Raspberry Pi 运行 Raspberry Pi OS.
示例代码
这是对最小(大概)、可重现示例的尝试:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#include <sys/time.h>
#ifdef USE_LIBEDIT
# include <editline/readline.h>
#else
# include <readline/readline.h>
#endif
#define PERIOD_US 1000 // 1.0 ms
#define DELAY_NS 700000 // 0.7 ms
/* Timer settings. */
const struct itimerval interval_off = {
.it_value = { .tv_sec = 0, .tv_usec = 0 },
.it_interval = { .tv_sec = 0, .tv_usec = 0 }
};
const struct itimerval interval_1ms = {
.it_value = { .tv_sec = 0, .tv_usec = PERIOD_US },
.it_interval = { .tv_sec = 0, .tv_usec = PERIOD_US }
};
static void sigalrm_callback(int signum)
{
(void) signum;
// Simulate work by busy-wating for DELAY_NS.
struct timespec end, now;
clock_gettime(CLOCK_MONOTONIC, &end);
end.tv_nsec += DELAY_NS;
end.tv_sec += end.tv_nsec / 1000000000;
end.tv_nsec %= 1000000000;
do
clock_gettime(CLOCK_MONOTONIC, &now);
while (now.tv_sec < end.tv_sec
|| (now.tv_sec == end.tv_sec && now.tv_nsec < end.tv_nsec));
}
static int should_quit = 0;
/* Interpret and free line. */
void interpret(char *line)
{
if (!line) {
printf("Got NULL line\n");
} else if (line[0] == '[=10=]') {
/* Ignore empty line. */
} else if (strcmp(line, "help") == 0) {
puts("help print this help\n"
"start start the interval timer at 1 kHz\n"
"stop stop the interval timer\n"
"quit end the program");
} else if (strcmp(line, "start") == 0) {
setitimer(ITIMER_REAL, &interval_1ms, NULL);
printf("Periodic timer started.\n");
} else if (strcmp(line, "stop") == 0) {
setitimer(ITIMER_REAL, &interval_off, NULL);
printf("Periodic timer stopped.\n");
} else if (strcmp(line, "quit") == 0) {
should_quit = 1;
} else {
printf("Unknown command \"%s\".\n", line);
}
free(line);
}
int main(void)
{
/* Catch SIGALRM. */
struct sigaction action;
action.sa_handler = sigalrm_callback;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
sigaction(SIGALRM, &action, NULL);
/* Process commands. */
while (!should_quit) {
char *line = readline("> ");
interpret(line);
}
return EXIT_SUCCESS;
}
使用
编译
gcc -O2 readline-alrm.c -lreadline -o readline-alrm
或
gcc -O2 -DUSE_LIBEDIT readline-alrm.c -ledit -o readline-alrm
编辑:我把命令解释器移出了main()
。
我找到了解决办法。这个问题可以通过使用异步来解决
readline 的“备用”接口。在示例程序中
问题,将 main()
替换为:
#include <unistd.h>
#include <errno.h>
#include <sys/select.h>
int main(void)
{
/* Catch SIGALRM. */
struct sigaction action;
action.sa_handler = sigalrm_callback;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
sigaction(SIGALRM, &action, NULL);
/* Process commands. */
rl_callback_handler_install("> ", interpret);
while (!should_quit) {
fd_set fds;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
int ret = select(STDIN_FILENO + 1, &fds, NULL, NULL, NULL);
#ifdef FIX_BUG
/* Restart on interrupted system call. */
if (ret == -1 && errno == EINTR)
continue;
#endif
if (FD_ISSET(STDIN_FILENO, &fds))
rl_callback_read_char();
}
putchar('\n');
rl_callback_handler_remove();
return EXIT_SUCCESS;
}
请注意,只有在定义了宏 FIX_BUG
时,问题才会得到解决。
这是我对问题的理解。 select()
中断时
通过信号,它 returns -1
和 errno
设置为 EINTR
。当这个
发生了,大多数时候文件描述符集 fds
结果是空的。
然而,在某些情况下,select()
将 STDIN_FILENO
留在 fds
内。
在这种情况下,程序应该 而不是 尝试处理它的输入。如果
我们打电话给 rl_callback_read_char()
尽管 select()
已经返回
-1
,然后我们有双重回声问题。
我想知道这是否是 readline()
实现中的错误。
也许它是在这个异步接口之上实现的,但它失败了
检查 select()
?
返回的值
我正在编写一个 Linux 与一些自定义硬件交互的程序
通过 GPIO 线。它使用一个计时器定期传送 SIGALRM
为了管理这些交互的时间,并且有相当
在信号处理程序中执行的一些工作。该计划提供了一个
命令行界面,我很想有行编辑
libreadline 提供的便利。不幸的是,它似乎
libreadline 不能很好地处理那些周期性信号。我也试过
libedit (a.k.a. “editline”) 的 readline 兼容模式,与
运气不好。
有没有一种简单的方法可以在程序中具有 readline 功能
收到大量 SIGALRM
信号?
症状
使用libreadline时,如果定时器是运行,一些字符 输入的内容随机回显两次。例如,键入“help”可能会导致 在终端上显示的“帮助”中。问题只是显而易见的 在回声中:程序确实收到了键入的单词(即“帮助”)。
在readline兼容模式下使用libedit时,如果定时器是
运行, readline()
returns NULL
每当被
SIGALRM
信号。
当计时器停止时,一切都按预期工作,两者都 libreadline 和 libedit。
环境
Ubuntu 20.04 带有最新的 apt 包 libreadline-dev(版本 8.0-4) 和 libedit-dev (3.1-20191231-1)。该程序最终将 部署在 Raspberry Pi 运行 Raspberry Pi OS.
示例代码
这是对最小(大概)、可重现示例的尝试:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#include <sys/time.h>
#ifdef USE_LIBEDIT
# include <editline/readline.h>
#else
# include <readline/readline.h>
#endif
#define PERIOD_US 1000 // 1.0 ms
#define DELAY_NS 700000 // 0.7 ms
/* Timer settings. */
const struct itimerval interval_off = {
.it_value = { .tv_sec = 0, .tv_usec = 0 },
.it_interval = { .tv_sec = 0, .tv_usec = 0 }
};
const struct itimerval interval_1ms = {
.it_value = { .tv_sec = 0, .tv_usec = PERIOD_US },
.it_interval = { .tv_sec = 0, .tv_usec = PERIOD_US }
};
static void sigalrm_callback(int signum)
{
(void) signum;
// Simulate work by busy-wating for DELAY_NS.
struct timespec end, now;
clock_gettime(CLOCK_MONOTONIC, &end);
end.tv_nsec += DELAY_NS;
end.tv_sec += end.tv_nsec / 1000000000;
end.tv_nsec %= 1000000000;
do
clock_gettime(CLOCK_MONOTONIC, &now);
while (now.tv_sec < end.tv_sec
|| (now.tv_sec == end.tv_sec && now.tv_nsec < end.tv_nsec));
}
static int should_quit = 0;
/* Interpret and free line. */
void interpret(char *line)
{
if (!line) {
printf("Got NULL line\n");
} else if (line[0] == '[=10=]') {
/* Ignore empty line. */
} else if (strcmp(line, "help") == 0) {
puts("help print this help\n"
"start start the interval timer at 1 kHz\n"
"stop stop the interval timer\n"
"quit end the program");
} else if (strcmp(line, "start") == 0) {
setitimer(ITIMER_REAL, &interval_1ms, NULL);
printf("Periodic timer started.\n");
} else if (strcmp(line, "stop") == 0) {
setitimer(ITIMER_REAL, &interval_off, NULL);
printf("Periodic timer stopped.\n");
} else if (strcmp(line, "quit") == 0) {
should_quit = 1;
} else {
printf("Unknown command \"%s\".\n", line);
}
free(line);
}
int main(void)
{
/* Catch SIGALRM. */
struct sigaction action;
action.sa_handler = sigalrm_callback;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
sigaction(SIGALRM, &action, NULL);
/* Process commands. */
while (!should_quit) {
char *line = readline("> ");
interpret(line);
}
return EXIT_SUCCESS;
}
使用
编译gcc -O2 readline-alrm.c -lreadline -o readline-alrm
或
gcc -O2 -DUSE_LIBEDIT readline-alrm.c -ledit -o readline-alrm
编辑:我把命令解释器移出了main()
。
我找到了解决办法。这个问题可以通过使用异步来解决
readline 的“备用”接口。在示例程序中
问题,将 main()
替换为:
#include <unistd.h>
#include <errno.h>
#include <sys/select.h>
int main(void)
{
/* Catch SIGALRM. */
struct sigaction action;
action.sa_handler = sigalrm_callback;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
sigaction(SIGALRM, &action, NULL);
/* Process commands. */
rl_callback_handler_install("> ", interpret);
while (!should_quit) {
fd_set fds;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
int ret = select(STDIN_FILENO + 1, &fds, NULL, NULL, NULL);
#ifdef FIX_BUG
/* Restart on interrupted system call. */
if (ret == -1 && errno == EINTR)
continue;
#endif
if (FD_ISSET(STDIN_FILENO, &fds))
rl_callback_read_char();
}
putchar('\n');
rl_callback_handler_remove();
return EXIT_SUCCESS;
}
请注意,只有在定义了宏 FIX_BUG
时,问题才会得到解决。
这是我对问题的理解。 select()
中断时
通过信号,它 returns -1
和 errno
设置为 EINTR
。当这个
发生了,大多数时候文件描述符集 fds
结果是空的。
然而,在某些情况下,select()
将 STDIN_FILENO
留在 fds
内。
在这种情况下,程序应该 而不是 尝试处理它的输入。如果
我们打电话给 rl_callback_read_char()
尽管 select()
已经返回
-1
,然后我们有双重回声问题。
我想知道这是否是 readline()
实现中的错误。
也许它是在这个异步接口之上实现的,但它失败了
检查 select()
?