Отправка SIGINT в разветвленный exec-процесс, который запускает скрипт, не убивает его - PullRequest
1 голос
/ 20 октября 2019

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

Отправка того же сигнала обычному исполняемому файлу работает нормально.

Пример:

Bash-скрипт, который просто имитирует долго работающий скрипт (work.sh):

#! /bin/bash

COUNTER=0
while true
do
    ((COUNTER+=1))
    echo "#${COUNTER} Working..."
    sleep 1
done

Код C:

int main() {
    pid_t pid = fork();
    if (pid == 0) {
        char* cmds[] = { "./work.sh", NULL };
        if (execvp(cmds[0], cmds) == -1) {
            exit(1);
        }
    } else {
        sleep(5);
        kill(pid, SIGINT);
    }

    return 0;
}

После отправки сигнала основной процесс заканчивается, но скрипт продолжает печатать.

Есть ли какая-то другая обработка сигналов в скриптах? Как вам нужно остановить дочерний процесс, игнорируя то, что выполняется?

Ответы [ 2 ]

3 голосов
/ 20 октября 2019

A РЕШЕНИЕ:

Добавьте эту строку поверх work.sh сценария trap exit SIGINT, чтобы иметь явный обработчик SIGINT:

#! /bin/bash

trap exit SIGINT

COUNTER=0
while true
do
    ((COUNTER+=1))
    echo "#${COUNTER} Working..."
    sleep 1
done

Running work исполняемый файл теперь печатает:

#1 Working...
#2 Working...
#3 Working...
#4 Working...
#5 Working...

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

ПРОБЛЕМА:

Я нашел эту веб-страницу , связанную в комментарии к этому вопросу на Unix-стеке (дляради полноты, вот также веб-страница, связанная в принятом ответе.) Вот цитата, которая может объяснить, что происходит:

bash - одна из немногих оболочек, которые реализуют ожиданиеи совместный подход к выходу при обработке доставки SIGINT / SIGQUIT. При интерпретации скрипта, получив SIGINT, он не сразу выходит, а вместо этого ожидает возврата работающей в данный момент команды и завершает работу (убивая себя с помощью SIGINT), если эта команда также была убита этим SIGINT. Идея состоит в том, что если ваш скрипт вызывает vi, например, и вы нажимаете Ctrl + C в vi, чтобы отменить действие, это не должно рассматриваться как запрос на прерывание скрипта.

Итак, представьте, что вы пишетескрипт и этот скрипт обычно завершаются при получении SIGINT. Это означает, что если этот сценарий вызывается из другого сценария bash, Ctrl-C больше не будет прерывать этот другой сценарий.

Такого рода проблемы можно увидеть с реальными командами, которые по умолчанию выходят из SIGINT.

РЕДАКТИРОВАТЬ:

Я нашел еще один Unix стек обмена ответ , который объясняет это еще лучше. Если вы посмотрите на bash(1) справочные страницы, то следующее также довольно объяснительно:

Для не встроенных команд, запускаемых bash, обработчикам сигналов присвоены значения, унаследованные оболочкой от ее родителя. Когда управление заданиями не действует, асинхронные команды игнорируют SIGINT и SIGQUIT в дополнение к этим унаследованным обработчикам.

особенно с учетом того, что:

Сигналы, игнорируемые при входе в оболочку, не могут быть перехвачены, сброшены или перечислены.

В основном, работаетwork.sh запускает его в отдельной среде выполнения:

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

Сюда входят обработчики сигналов, которые (если они не указаны явно) будут игнорировать SIGINT и SIGQUIT по умолчанию.

2 голосов
/ 20 октября 2019

Попробовав ваш пример, я пришел к следующим выводам.

Даже при интерактивном запуске в терминале сценарий work.sh не реагирует на SIGINT, отправленный с другого терминала;но это происходит при прерывании с помощью [Ctrl][c] из собственного терминала.
Я полагаю, что в управлении терминалом есть какая-то магия из bash ...

Добавление явного управления SIGINT в вашкажется, что скрипт заставляет его фактически реагировать на SIGINT (независимо от источника, даже из вашего кода C).

#! /bin/bash

must_stop=""
function sigint_handler()
{
    echo "SIGINT"
    must_stop="1"
}
trap sigint_handler SIGINT

COUNTER=0
while [ -z "${must_stop}" ]
do
    ((COUNTER+=1))
    echo "#${COUNTER} Working..."
    sleep 1
done

Как указано в комментариях, если единственная цель - остановить этот скрипт, это может быть SIGHUP илиSIGTERM должен быть рассмотрен;SIGINT, как правило, больше связан с интерактивным нажатием [Ctrl][c] в терминале, чем с явной отправкой сигнала из программы.

Обратите также внимание на то, что захват сигнала с помощью trap дает возможность чистого выхода из цикла, а нечем в середине любой инструкции.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...