Как Ctrl-C завершает дочерний процесс?



Я пытаюсь понять, как CTRL+C завершает дочерний, но не родительский процесс. Я вижу такое поведение в некоторых оболочках скриптов, таких как bash где вы можете начать какой-то длительный процесс, а затем завершить его, введя CTRL -C и элемент управления возвращается в оболочку.



не могли бы вы объяснить, как это работает и, в частности, почему не родительский процесс (оболочки) прекращается?



имеет ли оболочка чтобы сделать некоторые специальные обработки CTRL+C событие и если да, что именно он делает?

637   5  

5 ответов:

сигналы по умолчанию обрабатываются ядром. В старых системах Unix было 15 сигналов, теперь их стало больше. Вы можете проверить </usr/include/signal.h> (или убить-l). CTRL+C сигнал с именем SIGINT.

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

все сигналы (но SIGKILL) может быть обработан программой.

и это то, что оболочка делает:

  • когда оболочка работает в интерактивном режиме, она имеет специальную обработку сигнала для этого режима.
  • при запуске программы, например find оболочка:
    • fork s сам
    • и для ребенка установите обработку сигнала по умолчанию
    • замените ребенка данной командой (например, найти)
    • при нажатии клавиши CTRL+C, родительская оболочка обрабатывает этот сигнал, но ребенок получит его - с действием по умолчанию-прекратить. (ребенок также может реализовать обработку сигналов)

вы можете trap сигналы в вашем сценарии оболочки тоже...

и вы можете установить обработку сигнала для интерактивной оболочки тоже попробовать ввести это в верхней части вы ~/.profile. (Убедитесь, что вы уже вошли в систему и протестировать его с другое терминал - вы можете заблокировать себя)

trap 'echo "Dont do this"' 2

теперь каждый время вы нажимаете CTRL+C в командной строке, он будет печатать сообщение. Не забудьте удалить линию!

если интересно, вы можете проверить простой старый /bin/sh обработка сигнала в исходном коде здесь.

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

во-первых, читать статья Википедии о интерфейсе терминала POSIX весь путь до конца.

на SIGINT сигнал генерируется дисциплиной терминальной линии и транслируется на все процессы в терминале группа процессов переднего плана. Ваша оболочка уже создала новую группу процессов для команды (или конвейера команд), которую вы запустили, и сообщила терминалу, что эта группа процессов является его (терминала) группой процессов переднего плана. Каждый параллельный командный конвейер имеет свою собственную группу процессов, а изображения командный конвейер-это конвейер с группой процессов, которую оболочка запрограммировала в терминал в качестве группы процессов переднего плана терминала. Переключение "заданий" между передним и задним планом (некоторые детали в сторону) - это вопрос оболочки, сообщающей терминалу, какая группа процессов теперь является передним планом.

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

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

ни один процесс не обязательно принудительно закрывается (хотя по умолчанию, если вы не обрабатываете sigint, я считаю, что поведение должно вызывать abort(), но мне нужно посмотреть это).

конечно, запущенный процесс изолирован от оболочки, которая его запустила.

Если вы хотел родительская оболочка, чтобы пойти, запустите программу с exec:

exec ./myprogram

таким образом, родительская оболочка заменяется дочерним процессом

CTRL+C - это карта для команды kill. Когда вы нажимаете их, kill посылает сигнал SIGINT, который прерывает процесс.

убить:http://en.wikipedia.org/wiki/Kill_ (команда)

SIGINT:http://en.wikipedia.org/wiki/SIGINT_ (POSIX)

setpgid POSIX C Группа процессов минимальный пример

это может быть проще понять с минимальным запускаемым примером базового API.

это иллюстрирует, как сигнал действительно отправляется ребенку, если ребенок не изменил свою группу процессов с setpgid.

тогда, конечно, если ребенок обрабатывает сигнал, то он не убивается, как обычно.

главная.c:

#define _XOPEN_SOURCE 700
#include <assert.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void signal_handler(int sig) {
    char sigint_str[] = "sigint\n";
    if (sig == SIGINT) {
        write(STDOUT_FILENO, sigint_str, sizeof(sigint_str));
    }
    signal(sig, signal_handler);
}

int main(int argc, char **argv) {
    (void)(argv);
    pid_t pid, pgid;

    signal(SIGINT, signal_handler);
    signal(SIGUSR1, signal_handler);
    pid = fork();
    assert(pid != -1);
    if (pid == 0) {
        /* Change the pgid.
         * The new one is guaranteed to be different than the previous, which was equal to the parent's,
         * because `man setpgid` says:
         * > the child has its own unique process ID, and this PID does not match
         * > the ID of any existing process group (setpgid(2)) or session.
         */
        if (argc == 1) {
            setpgid(0, 0);
        }
        printf("child pid, pgid = %ju, %ju\n", (uintmax_t)getpid(), (uintmax_t)getpgid(0));
        assert(kill(getppid(), SIGUSR1) == 0);
        while (1);
        exit(EXIT_SUCCESS);
    }
    /* Wait until the child sends a SIGUSR1. */
    pause();
    pgid = getpgid(0);
    printf("parent pid, pgid = %ju, %ju\n", (uintmax_t)getpid(), (uintmax_t)pgid);
    /* man kill explains that negative first argument means to send a signal to a process group. */
    kill(-pgid, SIGINT);
    while (1);
}

скомпилировать и беги:

gcc -g -std=c99 -Wall -Wextra -o setpgid setpgid.c -lpthread
./setpgid

итог:

child pid, pgid = 28250, 28249
parent pid, pgid = 28249, 28249
sigint
sigint

и программа зависает.

pgid обоих процессов одинаковый, так как он наследуется через fork.

затем, если вы нажмете:

Ctrl + C

он снова выходы:

sigint
sigint

это как так:

  • отправить сигнал всей группе процессов с kill(-pgid, SIGINT)
  • Ctrl + C на терминале отправляет убийство всей группе процессов по умолчанию

если вы работаете с аргументом, например:

./setpgid 1

ребенок меняет свой pgid, и теперь только один sigint печатается каждый раз: родителя.

выйдите из программы, отправив другой сигнал процессам, например SIGQUIT с Ctrl + \.

если вы изменили идентификатор дочерней группы, то он также не получит этот SIGQUIT, и вы должны убить его явно:

ps aux | grep setpgid
kill -9 $PID

это ясно почему сигнал посылается всем процессам по умолчанию: в противном случае мы получим кучу процессов, чтобы быть очищены вручную.

протестировано на Ubuntu 18.04. GitHub вверх по течению.

Comments

    Ничего не найдено.