Kontroluj, który proces zostanie anulowany przez Ctrl + C

13

Mam płytę CD na żywo, która uruchamia się w systemie Linux i uruchamia mały skrypt Bash. Skrypt wyszukuje i uruchamia drugi program (zwykle jest to skompilowany plik binarny C ++).

Powinieneś być w stanie przerwać drugi program, naciskając Ctrl+ C. To, co powinno się stać, to zatrzymanie drugiego programu, a skrypt Bash kontynuuje czyszczenie. Tak naprawdę dzieje się tak, że zarówno główna aplikacja, jak i skrypt Bash kończą się. Co jest problemem.

Więc użyłem trapwbudowanego polecenia, aby powiedzieć Bashowi, aby zignorował SIGINT. A teraz Ctrl+ Ckończy aplikację C ++, ale Bash kontynuuje działanie. Świetny.

O tak ... Czasami „druga aplikacja” to kolejny skrypt Bash. I w takim przypadku Ctrl+ Cteraz nic nie robi .

Najwyraźniej moje rozumienie tego, jak to działa, jest błędne ... Jak kontrolować, który proces otrzymuje SIGINT, gdy użytkownik naciśnie Ctrl+ C? Chcę skierować ten sygnał tylko na jeden konkretny proces .

MathematicalOrchid
źródło

Odpowiedzi:

11

Po wielu, wielu godzinach poszukiwań w Internecie znalazłem odpowiedź.

  1. Linux ma pojęcie grupy procesów .

  2. Sterownik TTY ma pojęcie Foreground Process Group.

  3. Po naciśnięciu Ctrl+ CTTY wysyła SIGINTdo każdego procesu w grupie procesów pierwszego planu. (Zobacz także ten wpis na blogu .)

Dlatego skompilowany plik binarny i skrypt, który go uruchamia, zostają zablokowane. W rzeczywistości chcę tylko, aby główna aplikacja odbierała ten sygnał, a nie skrypty startowe.

Rozwiązanie jest teraz oczywiste: musimy umieścić aplikację w nowej grupie procesów i ustawić ją jako pierwszą grupę procesów dla tego TTY. Najwyraźniej polecenie to zrobić

setsid -c <applcation>

I to wszystko. Teraz, gdy użytkownik naciśnie Ctrl+ C, SIGINT zostanie wysłany do aplikacji (i wszystkich dzieci, które może mieć) i nikogo innego. Właśnie tego chciałem.

  • setsid sama w sobie umieszcza aplikację w nowej grupie procesów (w rzeczywistości cała nowa „sesja”, która najwyraźniej jest grupą grup procesów).

  • Dodanie -cflagi powoduje, że ta nowa grupa procesów staje się grupą procesów „pierwszego planu” dla bieżącego TTY. (Tj. Dostaje się, SIGINTgdy naciśniesz Ctrl+ C)

Widziałem wiele sprzecznych informacji o tym, kiedy Bash wykonuje lub nie uruchamia procesów w nowej grupie procesów. (W szczególności wygląda to inaczej w przypadku powłok „interaktywnych” i „nieinteraktywnych”.) Widziałem sugestie, że możesz sprawić, by działało to za pomocą sprytnych sztuczek z rurami ... Nie wiem. Ale powyższe podejście wydaje mi się skuteczne.

MathematicalOrchid
źródło
5
Prawie go masz ... kontrola zadań jest domyślnie wyłączona podczas uruchamiania skryptu, ale możesz ją włączyć za pomocą set -m. Jest to trochę czystsze i prostsze niż używanie za setsidkażdym razem, gdy prowadzisz dziecko.
psusi
@psusi Dzięki za wskazówkę! Potrzebuję tylko jednego dziecka, więc to nic wielkiego. Teraz już wiem, gdzie szukać w podręczniku Bash ...
MathematicalOrchid
Jak na ironię, mam odwrotny problem, w którym chcę, aby rodzic złapał sigint, ale nie dzieje się tak z powodu „sprytnego oszustwa z fajką”. Również grupa postępów -> grupa procesowa
Andrew Domaszek
2

Jak wspomniałem w komentarzu do f01, powinieneś wysyłać SIGTERM do procesu potomnego. Oto kilka skryptów, które pokazują, jak przechwytywać ^ C i wysyłać sygnał do procesu potomnego.

Najpierw rodzic.

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"

A teraz dziecko.

zapadka

#!/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"

Jeśli traptest wysyła SIGTERM, rzeczy zachowują się ładnie, ale jeśli traptest wysyła SIGINT, wtedy pętla nie widzi tego.

Jeśli pętla uśpienia wychwytuje SIGTERM, a tryb uśpienia jest na pierwszym planie, nie może zareagować na sygnał, dopóki nie obudzi się z bieżącego snu. Ale jeśli tryb uśpienia jest w tle, zareaguje natychmiast.

PM 2 Ring
źródło
Dziękuję za ten wspaniały przykład, który bardzo pomógł mi zrozumieć, jak mogę poprawić swój skrypt :)
TabeaKischka
2

W twoim skrypcie inicjującym bash.

  • śledzić PID drugiego programu

  • złap SIGINT

  • gdy złapiesz SIGINT, wyślij SIGINT do drugiego programu PID

f01
źródło
1
To może nie pomóc. Jeśli uwięzisz SIGINT w skrypcie nadrzędnym, wówczas skrypty potomne nie będą mogły odbierać SIGINT, niezależnie od tego, czy wysyłasz go od rodzica, czy z innej powłoki. Ale to nie jest takie złe, jak się wydaje, ponieważ możesz wysłać SIGTERM do dziecka, co i tak jest najwyraźniej preferowanym sygnałem.
PM 2Ring
1
Dlaczego SIGTERM jest „preferowany”?
MathematicalOrchid
Są prawie podobne. SIGINT to sygnał wysyłany przez kontrolujący terminal / użytkownika, np. Ctrl + C. SIGTERM jest również tym, co możesz wysłać, jeśli chcesz zakończyć proces. Więcej myśli tutaj en.wikipedia.org/wiki/Unix_signal
f01