Contrôlez le processus qui est annulé par Ctrl + C

13

J'ai un CD live qui démarre sous Linux et exécute un petit script Bash. Le script recherche et exécute un deuxième programme (qui est généralement un binaire C ++ compilé).

Vous êtes censé pouvoir abandonner le deuxième programme en appuyant sur Ctrl+ C. Ce qui devrait arriver, c'est que le deuxième programme s'arrête et que le script Bash continue d'exécuter le nettoyage. En réalité, l'application principale et le script Bash se terminent. C'est un problème.

J'ai donc utilisé la fonction trapintégrée pour dire à Bash d'ignorer SIGINT. Et maintenant Ctrl+ Ctermine l'application C ++, mais Bash continue son exécution. Génial.

Oh ouais ... Parfois, la "deuxième application" est un autre script Bash. Et dans ce cas, Ctrl+ Cmaintenant ne fait plus rien du tout .

De toute évidence, ma compréhension du fonctionnement de ces éléments est erronée ... Comment puis-je contrôler quel processus obtient SIGINT lorsque l'utilisateur appuie sur Ctrl+ C? Je veux diriger ce signal vers un seul processus spécifique .

MathematicalOrchid
la source

Réponses:

11

Après de nombreuses heures de recherches sur Internet, j'ai trouvé la réponse.

  1. Linux a la notion de groupe de processus .

  2. Le pilote TTY a une notion de groupe de processus de premier plan.

  3. Lorsque vous appuyez sur Ctrl+ C, le TTY envoie SIGINTà chaque processus du groupe de processus de premier plan. (Voir aussi cette entrée de blog .)

C'est pourquoi le binaire compilé et le script qui le lance sont tous les deux encombrés. En fait, je veux seulement que l'application principale reçoive ce signal, pas les scripts de démarrage.

La solution est maintenant évidente: nous devons placer l'application dans un nouveau groupe de processus et en faire le groupe de processus de premier plan pour cet ATS. Apparemment, la commande pour ce faire est

setsid -c <applcation>

Et c'est tout. Maintenant, lorsque l'utilisateur appuie sur Ctrl+ C, SIGINT sera envoyé à l'application (et à tous les enfants qu'elle peut avoir) et à personne d'autre. C'est ce que je voulais.

  • setsid en lui-même place l'application dans un nouveau groupe de processus (en fait, une toute nouvelle "session", qui est apparemment un groupe de groupes de processus).

  • L'ajout de l' -cindicateur fait de ce nouveau groupe de processus le groupe de processus "de premier plan" pour le téléscripteur en cours. (C'est à dire, il obtient SIGINTlorsque vous appuyez sur Ctrl+ C)

J'ai vu beaucoup d'informations contradictoires sur le moment où Bash exécute ou n'exécute pas les processus dans un nouveau groupe de processus. (En particulier, il semble que ce soit différent pour les obus "interactifs" et "non interactifs".) J'ai vu des suggestions que vous pouvez peut-être faire fonctionner cela avec une astuce de pipe intelligente ... Je ne sais pas. Mais l'approche ci-dessus semble fonctionner pour moi.

MathematicalOrchid
la source
5
Vous l'avez presque compris ... le contrôle des travaux est désactivé par défaut lors de l'exécution d'un script, mais vous pouvez l'activer avec set -m. C'est un peu plus propre et plus simple que d'utiliser setsidchaque fois que vous exécutez un enfant.
psusi
@psusi Merci pour le conseil! Je n'ai besoin que d'un enfant, donc ce n'est pas grave. Je sais maintenant où chercher dans le manuel de Bash ...
MathematicalOrchid
Ironiquement, j'ai le problème inverse où je veux que le parent attrape le sigint, mais ce n'est pas à cause de la «supercherie intelligente». Également groupe de progrès -> groupe de processus
Andrew Domaszek
2

Comme je l'ai mentionné dans le commentaire de f01, vous devriez envoyer SIGTERM au processus enfant. Voici quelques scripts qui montrent comment intercepter ^ C et envoyer un signal à un processus enfant.

Tout d'abord, le parent.

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"

Et maintenant, l'enfant.

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"

Si Traptest envoie SIGTERM, les choses se comportent bien, mais si Traptest envoie SIGINT, Sleeploop ne le voit jamais.

Si Sleeploop piège SIGTERM et que le mode veille est au premier plan, il ne peut pas répondre au signal jusqu'à ce qu'il se réveille du sommeil actuel. Mais si le mode veille est en arrière-plan, il répondra immédiatement.

PM 2Ring
la source
Merci pour ce bel exemple, cela m'a beaucoup aidé à comprendre comment je peux améliorer mon script :)
TabeaKischka
2

Dans votre script bash d'initiation.

  • garder une trace du PID du deuxième programme

  • attraper le SIGINT

  • lorsque vous avez attrapé un SIGINT, envoyez un SIGINT au deuxième programme PID

f01
la source
1
Cela peut ne pas aider. Si vous interceptez SIGINT dans le script parent, les scripts enfants ne pourront pas recevoir SIGINT, que vous l'envoyiez du parent ou d'un autre shell. Mais ce n'est pas aussi mauvais que cela puisse paraître, car vous pouvez envoyer SIGTERM à l'enfant, qui est apparemment le signal préféré à utiliser, de toute façon.
PM 2Ring
1
Pourquoi SIGTERM est-il "préféré"?
MathematicalOrchid
Ils sont presque similaires. SIGINT est le signal envoyé par le terminal / utilisateur de contrôle, par exemple Ctrl + C. SIGTERM est ce que vous pouvez également envoyer si vous souhaitez que le processus se termine. Plus de réflexions ici en.wikipedia.org/wiki/Unix_signal
f01