首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何使libreadline或libedit处理SIGALRM?

如何使libreadline或libedit处理SIGALRM?
EN

Stack Overflow用户
提问于 2021-09-09 13:39:49
回答 1查看 83关注 0票数 0

我正在编写一个Linux程序,它通过GPIO行与一些定制硬件进行交互。它使用一个定期传递SIGALRM的计时器来管理这些交互的定时,并且在信号处理程序中执行了相当多的工作。该程序提供了一个命令行界面,我希望锂线提供行编辑的方便。不幸的是,锂线似乎不能很好地处理这些周期性信号。我还尝试了李贝迪 (a.k.a )的读行兼容性模式。“社论”),没有运气。

在接收大量SIGALRM信号的程序中,有一种简单的方法可以获得读行功能吗?

症状

当使用libreadline时,如果计时器正在运行,一些键入的字符会被随机回显两次。例如,键入"help“可能会导致终端上显示"heelp”。这个问题只在echo中很明显:程序确实收到键入的单词(即“帮助”)。

当在读行兼容模式中使用libedit时,如果计时器正在运行,readline()将在被SIGALRM信号中断时返回NULL

当计时器停止时,所有的事情都会像预期的那样工作,无论是libreadline还是libedit。

环境

Ubuntu20.04提供了最新的apt包libreadline(Version8.0-4)和libedit-dev (3.1-20191231-1)。该程序最终将部署在运行Raspberry Pi操作系统的Raspberry Pi上。

示例代码

下面是一个最小(Ish)可复制的示例的尝试:

代码语言:javascript
复制
#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] == '\0') {
        /* 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;
}

编译或用

代码语言:javascript
复制
gcc -O2 readline-alrm.c -lreadline -o readline-alrm

代码语言:javascript
复制
gcc -O2 -DUSE_LIBEDIT readline-alrm.c -ledit -o readline-alrm

编辑:我将命令解释器移出main()

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-09-10 08:41:02

我找到了解决办法。这个问题可以通过使用readline的异步“备用”接口来解决。在问题中的示例程序中,将main()替换为:

代码语言:javascript
复制
#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()被信号中断时,它返回-1,并将errno设置为EINTR。当发生这种情况时,大多数情况下,文件描述符设置为fds的结果为空。然而,在某些情况下,select()STDIN_FILENO放在fds中。在这种情况下,程序不应该试图处理其输入。如果我们调用rl_callback_read_char(),尽管select()返回了-1,那么我们就有了双回显问题。

我想知道这是否是readline()实现中的一个bug。可能它是在这个异步接口之上实现的,并且它无法检查select()返回的值。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69119275

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档