Контроль, какой процесс отменяется с помощью Ctrl + C

13

У меня есть live CD, который загружается в Linux и запускает небольшой скрипт Bash. Скрипт ищет и запускает вторую программу (обычно это скомпилированный бинарный файл C ++).

Вы должны быть в состоянии прервать вторую программу, нажав Ctrl+ C. Что должно произойти, так это то, что вторая программа останавливается, и скрипт Bash продолжает выполнять очистку. На самом деле происходит то, что основное приложение и скрипт Bash завершаются. Что является проблемой.

Поэтому я использовал trapвстроенную функцию, чтобы сказать Bash игнорировать SIGINT. И теперь Ctrl+ Cзавершает работу приложения C ++, но Bash продолжает работать. Отлично.

Ах да ... Иногда "второе приложение" - это другой скрипт Bash. И в этом случае Ctrl+ Cтеперь ничего не делает .

Ясно, что мое понимание того, как это работает, неверно ... Как я могу контролировать, какой процесс получает SIGINT, когда пользователь нажимает Ctrl+ C? Я хочу направить этот сигнал только на один конкретный процесс .

MathematicalOrchid
источник

Ответы:

11

После многих, многих часов поиска лица в Интернете я нашел ответ.

  1. В Linux есть понятие группы процессов .

  2. Драйвер TTY имеет понятие группы процессов переднего плана.

  3. При нажатии кнопки Ctrl+ C, то TTY посылает SIGINTк каждому процессу в Foreground Process Group. (Смотрите также эту запись в блоге .)

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

Решение теперь очевидно: нам нужно поместить приложение в новую группу процессов и сделать ее группой процессов Foreground для этого TTY. Видимо команда для этого

setsid -c <applcation>

И это все. Теперь, когда пользователь нажимает Ctrl+ C, SIGINT будет отправлено в приложение (и любые дочерние элементы, которые у него могут быть), и больше никому. Что я и хотел.

  • setsid сам по себе помещает приложение в новую группу процессов (на самом деле, целую новую «сессию», которая, по-видимому, представляет собой группу групп процессов).

  • Добавление -cфлага превращает эту новую группу процессов в группу процессов «переднего плана» для текущего TTY. (Т.е. получается SIGINTкогда нажимаешь Ctrl+ C)

Я видел много противоречивой информации о том, когда Bash выполняет или не запускает процессы в новой группе процессов. (В частности, он выглядит по-разному для «интерактивных» и «неинтерактивных» оболочек.) Я видел предположения, что вы, возможно, можете заставить это работать с хитрым мошенническим трюком ... Я не знаю. Но подход выше, кажется, работает для меня.

MathematicalOrchid
источник
5
Вы почти поняли ... Управление заданиями отключено по умолчанию при запуске скрипта, но вы можете включить его с помощью set -m. Это немного чище и проще, чем использовать setsidкаждый раз, когда вы запускаете ребенка.
psusi
@psusi Спасибо за совет! Мне нужен только один ребенок, так что это не страшно. Теперь я знаю, где искать в руководстве по Bash ...
MateticOrchid
По иронии судьбы, у меня есть обратная проблема, когда я хочу, чтобы родитель поймал sigint, но это не из-за «хитрой хитрости в трубе». Также группа прогресса -> группа процессов
Андрей Домашек
2

Как я упоминал в комментарии к f01, вы должны отправлять SIGTERM дочернему процессу. Вот несколько сценариев, которые показывают, как перехватывать ^ C и отправлять сигнал дочернему процессу.

Во-первых, родитель.

TRAPtest

#!/bin/bash

# trap test
# Written by PM 2Ring 2014.10.23

myname=$(basename "$0")
child=sleeploop

set_trap()
{
    sig=$1
    msg="echo -e \"\n$myname received ^C, sending $sig to $child, $pid\""
    trap "$msg; kill -s $sig $pid" SIGINT
}
trap "echo \"bye from $myname\"" EXIT

echo "running $child..."
./$child 5  &
pid=$!

# set_trap SIGINT
set_trap SIGTERM
echo "$child pid = $pid"

wait $pid
echo "$myname finished waiting"

А теперь ребенок.

sleeploop

#!/bin/bash

# child script for traptest
# Written by PM 2Ring 2014.10.23

myname=$(basename "$0")
delay="$1"

set_trap()
{
    sig=$1
    trap "echo -e '\n$myname received $sig signal';exit 0" $sig
}

trap "echo \"bye from $myname\"" EXIT
set_trap SIGTERM
set_trap SIGINT

#Select sleep mode
if false
then
    echo "Using foreground sleep"
    Sleep()
    {
        sleep $delay
    }
else
    echo "Using background sleep"
    Sleep()
    {
        sleep "$delay" &
        wait $!
    }
fi

#Time to snooze :)
for ((i=0; i<5; i++));
do
    echo "$i: sleeping for $delay"
    Sleep
done

echo "$myname terminated normally"

Если traptest отправляет SIGTERM, все идет хорошо, но если traptest отправляет SIGINT, sleeploop никогда его не увидит.

Если sleeploop перехватывает SIGTERM, и режим сна находится на переднем плане, то он не может ответить на сигнал, пока не выйдет из текущего сна. Но если спящий режим является фоновым, он немедленно отреагирует.

PM 2Ring
источник
Спасибо за этот замечательный пример, он мне очень помог в понимании того, как я могу улучшить свой сценарий :)
TabeaKischka
2

В вашем первоначальном скрипте bash.

  • отслеживать PID второй программы

  • поймать сигнал

  • когда вы поймали SIGINT, отправьте SIGINT на второй PID программы

f01
источник
1
Это может не помочь. Если вы перехватите SIGINT в родительском скрипте, тогда дочерние скрипты не смогут получать SIGINT, отправляете ли вы его из родительского или из другой оболочки. Но это не так плохо, как кажется, потому что вы можете отправить SIGTERM ребенку, который, очевидно, является предпочтительным сигналом для использования в любом случае.
PM 2Ring
1
Почему SIGTERM "предпочтительнее"?
Математическая
Они почти похожи. SIGINT - это сигнал, посылаемый управляющим терминалом / пользователем, например, Ctrl + C. SIGTERM - это то, что вы также можете отправить, если хотите завершить процесс. Больше мыслей здесь en.wikipedia.org/wiki/Unix_signal
f01