Я пытаюсь написать мини-оболочку на C, используя это шаблон. Но всякий раз, когда я пытаюсь использовать интерактивные команды, такие как less и vi, оболочка застревает на waitpid (при включенном WUNTRACED эти команды возвращаются немедленно, потому что они останавливаются сигналом управления заданием как обозначено ps). Другие команды, которые не требуют ввода, такие как ls, подходят. Основная причина - setpgid, который, кажется, помещает разветвленный дочерний процесс (такой как less и vi) в другую группу процессов, которая больше не имеет общего терминала. Таким образом, дочерний процесс останавливается сигналом управления заданием. Удаление setpgid заставит мини-оболочку снова работать, но ее нельзя удалить, поскольку мини-оболочка должна управлять своими процессами переднего плана как группой (например, если процесс переднего плана P разветвляет дополнительные процессы P1 и P2, оболочка, получив SIGTSTP от пользователя, должна остановить P, P1 и P2. Это может быть удобно, если P, P1, P2 находятся в той же группе процессов, чей pgid совпадает с pid P. Мы можем просто отправить SIGTSTP всей группе процессов).

Я попытался использовать tcsetpgrp, чтобы исправить свою оболочку. Хотя он снова сделает такие команды, как vi, функциональными, мини-оболочка автоматически завершит работу после завершения разветвленного дочернего элемента, предположительно из-за того, что родительская оболочка ошибочно рассматривает завершение разветвленного дочернего элемента, а также завершение мини -ракушка.

Есть ли исправление, которое позволит мне сохранить setpgid?

// full code is in the provided link
if (!builtin_command(argv)) {
    if ((pid = Fork()) == 0) {   /* Child runs user job */
        if (execve(argv[0], argv, environ) < 0) {
            execvp(argv[0], argv);
            printf("%s: Command not found.\n", argv[0]);
            exit(0);
        }
    }
    // call wrapper function for error handling
    // set process group id of child to the pid of child
    Setpgid(pid, 0);
    if (!bg) {
        // foreground process, should wait for completion
        // tcsetpgrp does make vi and less work, 
        // but their completion also terminates the mini-shell
        // tcsetpgrp(STDERR_FILENO, pid);
        int status;
        if (waitpid(pid, &status, 0) < 0) {
            unix_error("waitfg: waitpid error");
        }
    } else {
        // background process
        printf("%d %s", pid, cmdline);
    }
}
0
Peter Li 14 Мар 2021 в 20:58

1 ответ

Лучший ответ

Решение состоит в том, чтобы передать контроль над терминалом другой группе процессов с помощью tcsetpgrp, а когда дочерний процесс завершится, вернуть управление терминалом снова с помощью tcsetpgrp. Обратите внимание, что tcsetpgrp отправляет SIGTTOU вызывающей стороне если вызывающий процесс принадлежит к группе фоновых процессов, значит, SIGTTOU и SIGTTIN должны быть заблокированы.

// error handling is omitted for brevity
// these two signal functions can be anywhere before tcsetpgrp
// alternatively, they can be masked during tcsetpgrp
signal(SIGTTOU, SIG_IGN);
signal(SIGTTIN, SIG_IGN);

if (!builtin_command(argv)) {
    if ((pid = Fork()) == 0) {
        if (execve(argv[0], argv, environ) < 0) {
            execvp(argv[0], argv);
            printf("%s: Command not found.\n", argv[0]);
            exit(0);
        }
    }
    if (!bg) {
        setpgid(pid, 0);
        tcsetpgrp(STDERR_FILENO, pid);
        int status;
        if (waitpid(pid, &status, 0) < 0) {
            unix_error("waitfg: waitpid error");
        }
        tcsetpgrp(STDERR_FILENO, getpgrp());
    } else {
        printf("%d %s", pid, cmdline);
    }
}

Это довольно грубая реализация. Проконсультируйтесь с комментариями Крейга по этому вопросу, чтобы узнать, где найти реализацию bash.

1
Peter Li 14 Мар 2021 в 22:02