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)
,但这似乎不是一个好主意.
起初我被jancheta的回答弄糊涂了,直到我发现目的siglongjmp
是在做跳转之前解除信号掩码中接收到的信号的阻塞。信号在信号处理程序的入口处被阻塞,以便处理程序不会中断自身。当我们恢复正常执行时,我们不想让信号阻塞,这就是为什么我们使用siglongjmp
代替longjmp
. AIUI,这只是简写,我们也可以调用sigprocmask
后跟longjmp
,这似乎是glibc在siglongjmp
.
我认为跳转可能不安全,因为readline()
调用malloc
和free
. 如果在某些异步信号不安全函数(例如malloc
或free
正在修改全局状态)时接收到信号,则如果我们随后跳出信号处理程序,可能会导致某些损坏。但是 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线.
对我来说,创建跳转似乎很麻烦且容易出错。我添加此支持的 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)
这在其他地方不需要其他额外的代码来让它工作——没有全局变量,没有设置跳转。