Странная проблема с ловушкой и SIGINT

Странная проблема с ловушкой и SIGINT

Пожалуйста, объясните это:

#!/bin/bash
# This is scripta.sh
./scriptb.sh &
pid=$!
echo $pid started
sleep 3
while true
do
    kill -SIGINT $pid
    echo scripta.sh $$
    sleep 3
done 

-

#!/bin/bash
# This is scriptb.sh
trap "echo Ouch;" SIGINT

while true
do
 echo scriptb.sh $$
 sleep 1
done

Когда я запускаю ./scripta.shловушку, она не печатает. Если я переключаюсь с SIGINT на любой другой сигнал (я пробовал SIGTERM, SIGUSR1), ловушка печатает "Ouch", как и ожидалось. Как это может происходить?

решение1

Если я переключусь с SIGINT на любой другой сигнал (я пробовал SIGTERM, SIGUSR1), ловушка выведет «Ouch», как и ожидалось.

Судя по всему, вы не пробовали SIGQUIT; вы, вероятно, обнаружите, что он ведет себя так же, как SIGINT.

Проблема заключается в контроле за работой.

В ранние дни Unix, всякий раз, когда оболочка помещала процесс или конвейер в фоновый режим, она заставляла эти процессы игнорировать SIGINT и SIGQUIT, поэтому они не завершались, если пользователь впоследствии нажимал Ctrl+ C(прерывание) или Ctrl+ \(выход) для приоритетной задачи. Когда появилось управление заданиями, оно принесло с собой группы процессов, и теперь все, что нужно сделать оболочке, это поместить фоновое задание в новую группу процессов; пока это не текущая терминальная группа процессов, процессы не будут видеть сигналы, поступающие с клавиатуры ( Ctrl+ C, Ctrl+ \ и Ctrl+ Z(SIGTSTP)). Оболочка может оставлять фоновые процессы с помощью расположения сигналов по умолчанию. Фактически, вероятно, так и должно быть, чтобы процессы можно было завершить с помощью Ctrl+ C, когда они переводятся на передний план.

Но неинтерактивные оболочки не используют управление заданиями. Логично, что неинтерактивная оболочка вернется к старому поведению игнорирования SIGINT и SIGQUIT для фоновых процессов по исторической причине — чтобы позволить фоновым процессам продолжать работать, даже если им посылаются сигналы клавиатуры. И скрипты оболочки запускаются в неинтерактивных оболочках.

И, если вы посмотрите на последний абзац под trapкомандой вбаш(1), вот увидишь

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

Итак, если вы бежите ./scriptb.sh & от своегоинтерактивныйкомандная строка оболочки, ее сигнальные диспозиции остаются нетронутыми (даже если она переводится в фоновый режим), и команда trapработает так, как и ожидалось. Но если вы запустите ./scripta.sh(с или без &), он запустит скрипт в неинтерактивной оболочке. И когда эта неинтерактивная оболочка запустится ./scriptb.sh &, он настроит scriptbпроцесс на игнорирование прерываний и выход. И поэтому trapкоманда в scriptb.shмолчании завершается неудачей.

решение2

С некоторой трассировкой:

strace -o aaa ./scripta

мы можем наблюдать, что по умолчанию

read(255, "#!/bin/bash\n# this is scripta.sh"..., 153) = 153
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
rt_sigprocmask(SIG_BLOCK, [INT CHLD], [], 8) = 0
lseek(255, -108, SEEK_CUR)              = 45
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f1ee9b7ca10) = 2483

так что scriptaзаблокировал INTи CHLDсигналы; scriptbнаследует эти настройки через fork(здесь называется clone). И что scriptbделает? Если мы запустим его scriptaчерез:

strace -o bbb ./scriptb &

А затем, если поискать что-то, связанное с сигналом, мы найдем:

rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7fb1b2e75250}, {SIG_IGN, [], 0}, 8) = 0
rt_sigaction(SIGINT, {SIG_IGN, [], SA_RESTORER, 0x7fb1b2e75250}, {SIG_DFL, [], SA_RESTORER, 0x7fb1b2e75250}, 8) = 0

Это указывает на то, что ничего не блокируется, а затем INTсигналы сначала обрабатываются по умолчанию, а затем игнорируются. scriptbЗапуск непосредственно из оболочки ниже, straceнапротив, показывает:

rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7f2c3f6d4250}, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7f2c3f6d4250}, {SIG_DFL, [], SA_RESTORER, 0x7f2c3f6d4250}, 8) = 0
rt_sigprocmask(SIG_BLOCK, [INT], [], 8) = 0
rt_sigaction(SIGINT, {0x45fbf0, [], SA_RESTORER, 0x7f2c3f6d4250}, {SIG_DFL, [], SA_RESTORER, 0x7f2c3f6d4250}, 8) = 0
rt_sigprocmask(SIG_BLOCK, [INT CHLD], [], 8) = 0
rt_sigprocmask(SIG_BLOCK, ~[RTMIN RT_1], [INT CHLD], 8) = 0
...

Или никогда не игнорировать, прежде чем перейти к повторной обработке вызова сна. Хорошо. Э-э. Давайте поставим прокладку между scriptaи scriptb, которая сбрасывается SIGINTв состояние по умолчанию...

#include <getopt.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int ch;
    while ((ch = getopt(argc, argv, "h?")) != -1) {
        switch (ch) {
        case 'h':
        case '?':
        default:
            abort();
        }
    }
    argc -= optind;
    argv += optind;
    if (argc < 1) abort();

    signal(SIGINT, SIG_DFL);

    execvp(*argv, argv);
    abort();
    return 1;
}

Используется следующим образом:

$ make defaultsig
cc     defaultsig.c   -o defaultsig
$ grep defaultsig scripta.sh
./defaultsig ./scriptb.sh &
$ ./scripta.sh 
12510 started
scriptb.sh 12510
scriptb.sh 12510
scriptb.sh 12510
scripta.sh 12509
Ouch
scriptb.sh 12510
scriptb.sh 12510
scriptb.sh 12510
scripta.sh 12509
Ouch
...

Да, теперь работает хорошо. Однако я не знаю, почему bashтак себя ведет, может, подать им сообщение об ошибке? Код sig.cвыглядит очень сложным, и в другом месте больше обработки сигналов...

решение3

Странная проблема с ловушкой и SIGINT

Спасибо всем за ответы и за время, которое вы уделили изучению проблемы.

Позвольте мне подвести итоги и обобщить (с извинениями за то, что может показаться очевидным в дальнейшем):

1) Я забыл добавить в свой вопрос, что я также пробовал SIGQUIT, и он вел себя как SIGINT;

2) Исходя из этого, я уже подозревал, что проблема связана с расположением по умолчанию интерактивного bash для этих двух сигналов;

3) Их действие по умолчанию не происходит при взаимодействии с bash, потому что нет смысла выходить или прерывать что-либо, когда единственное, что у вас есть, это приглашение. Если вы хотите выйти из оболочки, просто введите exit;

4) Я не вижу, чтобы SIGQUIT и SIGINT играли особую роль в управлении заданиями (в отличие от SIGTSTP, SIGTTOU, SIGTTIN);

5) Мне не кажется логичным, что стандартное расположение интерактивного bash для этих двух сигналов будет унаследовано фоновой (неинтерактивной) оболочкой (в нашем случае той, которая выполняет scriptb.sh);

6) Фактически, так же как группа процессов переднего плана не наследует (от оболочки, которая ее запустила) диспозиции для SIGQUIT и SIGINT, IMHO, имело бы смысл, чтобы то же самое произошло и с группами фоновых процессов.

7) Более того, какова бы ни была унаследованная предрасположенность, ловушка должна ее изменить.

8) В целом я склонен согласиться с thrig и думать, что то, что мы здесь видим, — это ошибка.

Связанный контент