exe c процесс игнорирования сигналов C - PullRequest
1 голос
/ 26 апреля 2020

Я пишу оболочку в C и пытаюсь добавить обработку сигналов. В оболочке вызывается fork (), а дочерний процесс выполняет команду оболочки. Дочерний процесс помещается в свою собственную группу процессов. Таким образом, если нажать Ctrl- C, когда дочерний процесс находится на переднем плане, он закрывает все процессы, имеющие один и тот же идентификатор группы процессов. Оболочка выполняет команды, как и ожидалось.

Проблема в сигналах. Когда, например, я выполняю «sleep 5», а затем нажимаю Ctrl- C для SIGINT, появляется приглашение «shell>», как и ожидалось, но процесс все еще выполняется в фоновом режиме. Если я быстро нажму «ps» после нажатия Ctrl- C, спящий вызов все еще будет там. Затем, после 5 секунд, и я снова запускаю "ps", все прошло. То же самое происходит, когда я нажимаю Ctrl-Z (SIGTSTP). С SIGTSTP процесс переходит в фоновый режим, как и ожидалось, но не приостанавливает выполнение. Он продолжает работать, пока не закончится.

Почему эти процессы отправляются в фоновый режим и продолжают работать? Вот суть моего кода ...

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int status;

void sig_handler_parent()
{
    printf("\n");
}

void sig_handler_sigchild(int signum)
{
    waitpid(-1, &status, WNOHANG);
}

int main()
{
    signal(SIGCHLD, sig_handler_sigchild);
    signal(SIGINT, sig_handler_parent);
    signal(SIGQUIT, sig_handler_parent);
    signal(SIGTERM, sig_handler_parent);
    signal(SIGCONT, sig_handler_parent);
    signal(SIGTSTP, sig_handler_parent);

    while (1)
    {
        printf("shell> ");

        // GET COMMAND INPUT HERE

        pid = fork();

        if (pid == 0)
        {
            setpgid(getpid(), getpid());

            execvp(cmd[0], cmd);
            printf("%s: unknown command\n", cmd[0]);

            exit(1);
        }
        else
            waitpid(0, &status, WUNTRACED); 
    }

    return 0;
}

ps Я уже пытался установить все обработчики сигналов в SIG_DFL перед командой exe c.

Ответы [ 2 ]

1 голос
/ 26 апреля 2020

Вы уверены, что ваш дочерний процесс на самом деле получает сигналы от вашего tty? Я считаю, что вам нужно позвонить на tcsetpgrp, чтобы фактически сказать управляющему терминалу отправлять сигналы группе процессов вашего дочернего процесса.

Например, после того, как вы позвоните fork и до exec, попробуйте это изнутри вашего ребенка.

tcsetpgrp(STDIN_FILENO, getpid())

Вот справочная страница для tcsetpgrp(3)

0 голосов
/ 26 апреля 2020

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

Чтобы привести вас в порядок, я укажу на ряд фактов, которые вы, возможно, неправильно поняли. Надеюсь, что вместе с парой ссылок на документацию это будет полезно.

Обработка ошибок

Во-первых: имейте привычку обрабатывать ошибки, особенно если вы знаете, что есть что-то, чего вы не понимаете , Например, родитель (ваша оболочка) ждет, пока завершится дочерний процесс,

waitpid(0, &status, WUNTRACED); 

Вы говорите,

Когда, например, я выполняю «сон 5», а затем Я нажимаю Ctrl- C для SIGINT, приглашение «shell>» появляется, как и ожидалось, но процесс все еще выполняется в фоновом режиме.

На самом деле происходит, когда вы нажимаете Ctrl- C, родитель ( не дочерний; см. Ниже, почему) получает SIGINT (терминальная подсистема ядра обрабатывает ввод с клавиатуры, видит, что кто-то одновременно удерживает клавиши «Ctrl» и «C») время и приходит к выводу, что все процессы с этим управляющим терминалом должны быть отправлены SIGINT).

Изменить родительскую ветвь на,

int error = waitpid(0, &status, WUNTRACED); 
if (error != 0)
    perror("waitpid");

С этим вы perror() напечатайте что-то вроде:

waitpid: interrupted system call

Вы хотите SIGINT до go для ребенка, поэтому что-то должно быть не так.

Обработчики сигналов, fork() и exec()

Далее, что происходит с вашими обработчиками сигналов через fork() и exec()?

Th e справочная страница обзора сигналов состояния,

Дочерний элемент, созданный с помощью fork (2), наследует копию расположения сигналов своего родителя. Во время execve (2) расположение обработанных сигналов сбрасывается до значения по умолчанию; расположение игнорируемых сигналов остается неизменным.

Итак, в идеале это означает, что:

  1. Родитель (оболочка) видит SIGINT, как отмечено выше и печатает «прерванный системный вызов».
  2. Дочерние обработчики сигналов возвращаются к своим значениям по умолчанию. Для SIGINT это означает завершение.

Вы не возитесь с управляющим терминалом , поэтому дочерний объект наследует управляющий терминал родителя. Это означает, что SIGINT доставляется как родительскому , так и дочернему. Учитывая, что поведение SIGINT ребенка должно завершаться, я бы поспорил, что ни один процесс не остался запущенным.

За исключением случаев, когда вы используете setpgid() для создания новой группы процессов.

Процесс Группы, сеансы и управляющий терминал

Кто-то однажды назвал меня UNIX седобородым. Хотя это верно с визуальной точки зрения, я должен отказаться от этого комплимента, потому что я редко бываю в одном из самых темных углов UNIX - терминальной подсистеме. Авторы командной оболочки тоже должны это понимать.

В этом контексте это раздел "NOTES" справочной страницы setpgid() . Я предлагаю вам прочитать, особенно там, где написано:

В любое время одна (и только одна) из групп процессов в сеансе может быть основной группой процессов для терминала; (...)

Оболочка (bash возможно), из которой вы запускаете программу оболочки, сделала это для вызова вашей программы на переднем плане и пометила это как "группа процессов переднего плана". По сути это означает: «Пожалуйста, уважаемый терминал, всякий раз, когда кто-то нажимает Ctrl- C, отправьте SIGINT всем процессам в этой группе. Я (ваш родитель) просто сижу и жду (waitpid()), пока все не закончится, и снова получит контроль. ".

Вы создаете группу процессов для дочернего элемента, но не говорите об этом терминалу. Вам нужно

  1. Отсоединить родительский элемент от терминала.
  2. Установить дочернюю группу процессов в качестве передней группы процессов терминала.
  3. Подождите, пока child (у вас уже есть).
  4. Восстановить передний план терминала.

Далее в разделе «ЗАМЕЧАНИЯ» на этой странице руководства, они дают ссылки на то, как это делается. Следуйте им, внимательно читайте, пробуйте и убедитесь, что вы справляетесь с ошибками. В большинстве случаев такие ошибки являются признаками недопонимания. И в большинстве случаев такие ошибки исправляются путем перечитывания документации.

...