Readline:获取SIGINT的新提示

Vis*_*ota 12 c signals readline sigint

我使用readline获得了类似于以下内容的代码:

#include <errno.h>
#include <error.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <readline/readline.h>
#include <readline/history.h>

void handle_signals(int signo) {
  if (signo == SIGINT) {
    printf("You pressed Ctrl+C\n");
  }
}

int main (int argc, char **argv)
{
   //printf("path is: %s\n", path_string);
  char * input;
  char * shell_prompt = "i-shell> ";
  if (signal(SIGINT, handle_signals) == SIG_ERR) {
    printf("failed to register interrupts with kernel\n");
  }

  //set up custom completer and associated data strucutres
  setup_readline();

  while (1) 
  {
    input = readline(shell_prompt);
    if (!input)
      break;
    add_history(input);

    //do something with the code
    execute_command(input);

  }  
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

我已将其设置为拦截SIGINT(即用户按下Ctrl+C),因此我可以判断信号处理程序handle_signals()是否正常工作.但是,当控制返回时readline(),它使用的是输入之前使用的同一行文本.我想要发生的是readline"取消"当前文本行并给我一个新行,就像BASH shell一样.像这样的东西:

i-shell> bad_command^C
i-shell> _
Run Code Online (Sandbox Code Playgroud)

有机会让这个工作吗?我读过的邮件列表中提到的东西longjmp(2),但这似乎不是一个好主意.

Met*_*hic 9

起初我被jancheta的回答弄糊涂了,直到我发现目的siglongjmp是在做跳转之前解除信号掩码中接收到的信号的阻塞。信号在信号处理程序的入口处被阻塞,以便处理程序不会中断自身。当我们恢复正常执行时,我们不想让信号阻塞,这就是为什么我们使用siglongjmp代替longjmp. AIUI,这只是简写,我们也可以调用sigprocmask后跟longjmp,这似乎是glibc在siglongjmp.

我认为跳转可能不安全,因为readline()调用mallocfree. 如果在某些异步信号不安全函数(例如mallocfree正在修改全局状态)时接收到信号,则如果我们随后跳出信号处理程序,可能会导致某些损坏。但是 Readline 安装了自己的信号处理程序,对此非常小心。他们只是设置了一个标志然后退出;当 Readline 库再次获得控制权时(通常在中断的“read()”调用之后),它调用RL_CHECK_SIGNALS()然后使用kill(). 因此,使用siglongjmp()中断调用的信号退出信号处理程序是安全的readline()- 保证在异步信号不安全函数期间未收到该信号。

事实上,因为有几个电话给说法并不完全正确malloc()free()范围内rl_set_prompt(),这readline()只是前调用rl_set_signals()。我想知道这个调用顺序是否应该改变。在任何情况下,竞争条件的可能性都非常小。

我查看了 Bash 源代码,它似乎跳出了它的 SIGINT 处理程序。

您可以使用的另一个 Readline 接口是回调接口。它被 Python 或 R 等需要一次侦听多个文件描述符的应用程序使用,例如在命令行界面处于活动状态时判断绘图窗口是否正在调整大小。他们会select()循环执行此操作。

这是来自 Chet Ramey 的一条消息,它给出了在回调接口中收到 SIGINT 后如何获得类似 Bash 的行为的一些想法:

https://lists.gnu.org/archive/html/bug-readline/2016-04/msg00071.html

这些消息建议您执行以下操作:

    rl_free_line_state ();
    rl_cleanup_after_signal ();
    RL_UNSETSTATE(RL_STATE_ISEARCH|RL_STATE_NSEARCH|RL_STATE_VIMOTION|RL_STATE_NUMERICARG|RL_STATE_MULTIKEY);
    rl_line_buffer[rl_point = rl_end = rl_mark = 0] = 0;
    printf("\n");
Run Code Online (Sandbox Code Playgroud)

收到 SIGINT 后,您可以设置一个标志,然后在select()循环中检查该标志- 因为select()调用将被带有errno==EINTR. 如果发现已经设置了标志,则执行上面的代码。

我的观点是 Readline 应该在它自己的 SIGINT 处理代码中运行类似上面的片段。目前它或多或少只执行前两行,这就是为什么增量搜索和键盘宏之类的东西被 ^C 取消,但该行没有被清除。

另一张海报上写着“Call rl_clear_signals()”,这仍然让我感到困惑。我没有尝试过,但我不知道它如何完成任何事情,因为 (1) Readline 的信号处理程序无论如何都会将信号转发给您,并且 (2)readline()在进入时安装信号处理程序(并在退出时清除它们) ,因此它们通常不会在 Readline 代码之外处于活动状态。


小智 7

你使用longjmp的想法是正确的.但是因为longjmp将在信号处理程序中,所以你需要使用sigsetjmp/siglongjmp.

作为使用代码作为基础的快速示例:

#include <setjmp.h>
#include <errno.h>
#include <error.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <readline/readline.h>
#include <readline/history.h>

sigjmp_buf ctrlc_buf;

void handle_signals(int signo) {
  if (signo == SIGINT) {
    printf("You pressed Ctrl+C\n");
    siglongjmp(ctrlc_buf, 1);
  }
}

int my_cmd_loop(int argc, char **argv)
{
   //printf("path is: %s\n", path_string);
  char * input;
  char * shell_prompt = "i-shell> ";
  if (signal(SIGINT, handle_signals) == SIG_ERR) {
    printf("failed to register interrupts with kernel\n");
  }

  //set up custom completer and associated data strucutres
  setup_readline();

  while (1) 
  {
    while ( sigsetjmp( ctrlc_buf, 1 ) != 0 );

    input = readline(shell_prompt);
    if (!input)
      break;
    add_history(input);

    //do something with the code
    execute_command(input);

  }  
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

siglongjmp将一个非0的值(在本例中为1)返回到sigsetjmp,因此while循环再次调用sigsetjmp(sigsetjmp的成功返回值为0),然后再次调用readline.

设置rl_catch_signals = 1然后调用也可能有帮助,rl_set_signals()以便readline信号处理在将信号传递给程序之前清除所需的任何变量,然后再跳回到readline线.

  • 你不能安全地从信号处理程序调用`printf`. (4认同)

Jam*_*lor 7

对我来说,创建跳转似乎很麻烦且容易出错。我添加此支持的 shell 实现不允许进行此更改。

幸运的是,readline有一个更清晰的替代解决方案。我的SIGINT处理程序看起来像这样:

static void
int_handler(int status) {
    printf("\n"); // Move to a new line
    rl_on_new_line(); // Regenerate the prompt on a newline
    rl_replace_line("", 0); // Clear the previous text
    rl_redisplay();
}
Run Code Online (Sandbox Code Playgroud)

这在其他地方不需要其他额外的代码来让它工作——没有全局变量,没有设置跳转。