处理信号并编写我自己的 shell

Handle signals and process writing my own shell

提问人:lfalkau 提问时间:3/20/2020 更新时间:3/21/2020 访问量:1036

问:

我正在尝试制作一个提示命令并等待用户输入的外壳。 我希望我的 shell 在用户按下时打印另一个提示,并在用户按下时退出。ctrl-cctrl-d

这是主循环:

int my_shell(char **env)
{
    char    *cmd_line;

    while (true)
    {
            print_prompt();
            cmd_line = get_cmd();
            process(cmd_line);
    }
    return (0);
}

我能够捕捉和发出信号,但我不知道如何构建主循环以在好地方退出。我尝试使用几个,但我做错了。ctrl-cctrl-dfork()wait()getpid()

这是我的一次尝试:


int extern_pid;
int intern_pid;

int my_shell(char **env)
{
    char    *cmd_line;

    extern_pid = getpid();
    while (true)
    {
        if (fork() == 0)
        {
            intern_pid = getpid();
            print_prompt();
            cmd_line = get_cmd();
            process(cmd_line);
            exit(0);
        }
        else
        {
            wait(0);
        }
    }
    return (0);
}

对于这些信号处理程序:

void ctrlc_handler(int signal)
{
    if (getpid() == intern_pid)
        exit(0);
}

void ctrld_handler(int signal)
{
    if (getpid() == extern_pid)
        exit(0);
}

注意:信号在函数中处理。ctrl-dget_cmd()

C Shell 过程 信号 EOF

评论

0赞 Mark Plotnick 3/21/2020
Ctrl-D 不发送信号。它只是将行中所有累积的字符传递给执行 的进程,而不等待(或包括)换行符。shell 通常在执行返回计数 0 的操作时退出,这对应于用户在行首键入 ctrl-d。readread
0赞 lfalkau 3/21/2020
是的,但我在阅读中处理了它,现在如果我按下它,它会发送一个信号

答:

2赞 Stephan Schlecht 3/21/2020 #1

无需使用 fork 创建子进程即可在自定义 shell 中处理 Ctrl-C 信号。一种可能性是将信号处理程序与 sigsetjmp/siglongjmp 一起使用。

程序如下:

  • 信号处理程序已安装
  • 在启动主循环之前,调用环境会使用 SIGSETJMP 存储在 env 中
  • 在信号处理程序中,调用 siglongjmp() 恢复 sigsetjmp() 保存的环境,并执行非本地跳转到调用 sigsetjmp 的位置

由于信号处理程序总是可以调用的,甚至可能在调用 sigsetjmp() 之前,因此必须确保 siglongjmp() 已经可以被调用。这是通过设置一个名为 jmp_set 的易失变量sig_atomic_t来完成的。

该函数只知道一个名为 的内部命令。正如在问题注释中已经指出的那样,如果用户在行首输入 Ctrl-D 作为第一个字符,这将自动导致 getchar 调用中的 EOF。然后,函数get_cmd在此处返回命令。或者,用户可以输入命令来结束程序。processexitexitexit

在函数中,该位置标有注释,您可能希望使用 fork/exec 调用外部程序。它返回一个布尔值,说明是否应该退出程序。也许也可以在这里相应地评估来自外部程序调用的状态代码。process

这是一个小型的、独立的示例程序,其中包含您的ctrlc_handler、get_cmd和进程函数布局,并使用 sigsetjmp()/siglongjmp() 进行了扩展,当然并不完整,但可能是一个起点:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <setjmp.h>

#define MAX_CMD_LENGTH 1024

static const char *const EXIT_CMD = "exit";
static sigjmp_buf env;
static volatile sig_atomic_t jmp_set;

static void ctrlc_handler(int signal) {
    if (jmp_set == 0)
        return;
    if (signal == SIGINT) {
        siglongjmp(env, 1);
    }
}

static char *get_cmd(void) {
    char *cmd_buf = calloc(MAX_CMD_LENGTH, sizeof(char));
    if (!cmd_buf) {
        perror("malloc failed\n");
        exit(1);
    }

    char *ptr = cmd_buf;
    int ch = getchar();
    while (ch != EOF && ch != '\n' && ptr < cmd_buf + MAX_CMD_LENGTH - 1) {
        *ptr++ = (char) ch;
        ch = getchar();
    }
    if (ch == EOF) {
        strcpy(cmd_buf, EXIT_CMD);
    }
    return cmd_buf;

}

static bool process(char *cmd) {
    if (strcmp(cmd, EXIT_CMD) == 0) {
        return true;
    }
    // a call to fork together with a function from the
    // exec family could be used to call external programs
    printf("process '%s'\n", cmd);
    return false;
}

static int cnt = 0;

int main(void) {
    if (signal(SIGINT, ctrlc_handler) == SIG_ERR) {
        perror("signal error");
        exit(1);
    }
    if (sigsetjmp(env, 1)) {
        printf("\n");
        cnt++;
    }
    jmp_set = 1;
    bool exit;
    do {
        printf("%d> ", cnt);
        char *cmd = get_cmd();
        exit = process(cmd);
        free(cmd);
    } while (!exit);
    return 0;
}

评论

0赞 lfalkau 3/21/2020
它工作得很好,这就是我一直在寻找的那种东西。不幸的是,这是一个学校项目,禁止使用 siglongjmp() / sigsetjmp() 函数。多亏了你的回答,我知道至少有一种方法可以在没有子进程的情况下实现它。我会继续寻找解决方案