Обработка сигналов и процесс написания моей собственной оболочки - PullRequest
0 голосов
/ 20 марта 2020

Я пытаюсь создать оболочку, которая запрашивает команду и ждет ввода пользователя. Я хочу, чтобы моя оболочка распечатала еще один запрос, когда пользователь нажал ctrl-c, и завершил работу, когда он нажал ctrl-d.

. Вот главное l oop:

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

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

Я в состоянии поймать сигналы ctrl-c и ctrl-d, но я не знаю, как структурировать основной l oop для выхода в хорошие места. Я попытался использовать несколько 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().

1 Ответ

1 голос
/ 21 марта 2020

Не нужно создавать дочерний процесс с помощью fork для обработки сигнала Ctrl- C в пользовательской оболочке. Одна возможность - использовать обработчик сигнала вместе с sigsetjmp / siglongjmp.

Процедура будет такой:

  • установлен обработчик сигнала
  • до запуска основного l oop, среда вызова сохраняется в env используя sigsetjmp
  • в обработчике сигнала, вызов siglongjmp () восстанавливает среду, сохраненную sigsetjmp (), и выполняет нелокальный переход к месту, где sigsetjmp был вызван

, так как обработчики сигналов всегда можно вызывать, возможно, даже перед вызовом sigsetjmp () необходимо убедиться, что siglongjmp () уже может быть вызван. Это делается путем установки изменчивой переменной sig_atomic_t с именем jmp_set.

Функция process знает только одну внутреннюю команду с именем exit. Как уже отмечалось в комментариях к вопросу, если пользователь вводит Ctrl-D в качестве первого символа в начале строки, это автоматически приводит к EOF в вызове getchar. Функция get_cmd затем возвращает здесь команду exit. Кроме того, пользователь может ввести команду exit для завершения программы.

В функции process позиция помечена комментарием, в котором вы, вероятно, захотите вызывать внешние программы с помощью fork / exe c. , Возвращает bool, должна ли программа быть закрыта или нет. Возможно, здесь можно было бы также оценить код состояния по вызовам внешних программ.

Вот небольшая, автономная примерная программа с вашим 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;
}
...