处理信号和进程编写我自己的 shell

Handle signals and process writing my own shell

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

这是主循环:

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

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

我能够捕捉到 ctrl-cctrl-d 信号,但我不知道如何构造主循环以在合适的地方退出。我尝试使用几个 fork()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-d 信号在 get_cmd() 函数中处理。

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

程序如下:

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

由于始终可以调用信号处理程序,甚至可能在调用 sigsetjmp() 之前,因此必须确保可以调用 siglongjmp()。这是通过设置一个名为 jmp_set.

的可变变量 sig_atomic_t 来完成的

函数 process 只知道一个名为 exit 的内部命令。正如问题的评论中已经指出的那样,如果用户在一行的开头输入 Ctrl-D 作为第一个字符,这会自动在 getchar 调用中产生 EOF。函数 get_cmd 然后 returns 命令 exit 在这里。或者,用户可以输入命令exit结束程序。

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

这是一个小型的、自包含的示例程序,包含您的 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;
}