Controlla quale processo viene annullato da Ctrl + C


13

Ho un CD live che si avvia su Linux ed esegue un piccolo script Bash. Lo script cerca ed esegue un secondo programma (che di solito è un binario C ++ compilato).

Dovresti essere in grado di interrompere il secondo programma premendo Ctrl+ C. Ciò che dovrebbe accadere è che il secondo programma si interrompe e lo script Bash continua a eseguire la pulizia. Ciò che realmente accade è che sia l'applicazione principale sia lo script Bash terminano. Qual è un problema.

Quindi ho usato il trapbuiltin per dire a Bash di ignorare SIGINT. E ora Ctrl+ Ctermina l'applicazione C ++, ma Bash continua a funzionare. Grande.

Oh sì ... A volte la "seconda applicazione" è un'altra sceneggiatura di Bash. E in quel caso, Ctrl+ Cora non fa nulla .

Chiaramente la mia comprensione di come funziona questa roba è sbagliata ... Come posso controllare quale processo ottiene SIGINT quando l'utente preme Ctrl+ C? Voglio indirizzare questo segnale a un solo processo specifico .

Risposte:


11

Dopo molte, molte ore di ricerche sul volto di Internet, ho trovato la risposta.

  1. Linux ha l'idea di un gruppo di processi .

  2. Il driver TTY ha un'idea di Foreground Process Group.

  3. Quando si preme Ctrl+ C, TTY invia SIGINTa tutti i processi nel gruppo di processi in primo piano. (Vedi anche questo post sul blog .)

Questo è il motivo per cui sia il binario compilato che lo script che lo avvia vengono entrambi bloccati. In effetti, desidero solo che l'applicazione principale riceva questo segnale, non gli script di avvio.

La soluzione è ora ovvia: dobbiamo inserire l'applicazione in un nuovo gruppo di processi e trasformarla in Foreground Process Group per questo TTY. Apparentemente il comando per farlo è

setsid -c <applcation>

E questo è tutto. Ora quando l'utente preme Ctrl+ C, SIGINT verrà inviato all'applicazione (e a tutti i suoi figli) e a nessun altro. È quello che volevo.

  • setsid di per sé inserisce l'applicazione in un nuovo gruppo di processi (in effetti, un'intera nuova "sessione", che apparentemente è un gruppo di gruppi di processi).

  • L'aggiunta del -cflag fa sì che questo nuovo gruppo di processi diventi il ​​gruppo di processi "in primo piano" per l'attuale TTY. (Cioè, si ottiene SIGINTquando si preme Ctrl+ C)

Ho visto molte informazioni contrastanti su quando Bash esegue o non esegue processi in un nuovo gruppo di processi. (In particolare, sembra essere diverso per le shell "interattive" e "non interattive".) Ho visto dei suggerimenti sul fatto che potresti riuscire a far funzionare tutto questo con l' inganno della pipa intelligente ... Non lo so. Ma l'approccio sopra sembra funzionare per me.


5
Hai quasi capito ... il controllo dei lavori è disabilitato di default quando si esegue uno script, ma è possibile abilitarlo con set -m. È un po 'più pulito e semplice rispetto all'uso setsidogni volta che si esegue un bambino.
psusi,

@psusi Grazie per la punta! Ho solo bisogno di gestire un bambino, quindi non è un grosso problema. Ora so dove cercare nel manuale di Bash ...
MathematicalOrchid,

Ironia della sorte, ho il problema inverso in cui voglio che il genitore catturi sigint, ma non a causa di un "inganno astuto". Anche gruppo di avanzamento -> gruppo di processo
Andrew Domaszek

2

Come ho detto nel commento a f01, dovresti inviare SIGTERM al processo figlio. Ecco un paio di script che mostrano come intercettare ^ C e inviare un segnale a un processo figlio.

Innanzitutto, il genitore.

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"

E ora il bambino.

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"

Se traptest invia SIGTERM le cose si comportano bene, ma se traptest invia SIGINT, sleeploop non lo vede mai.

Se sleeploop intrappola SIGTERM e la modalità di sospensione è in primo piano, non può rispondere al segnale fino a quando non si sveglia dal sonno corrente. Ma se la modalità di sospensione è in background, risponderà immediatamente.


Grazie per questo fantastico esempio, mi ha aiutato molto a capire come posso migliorare la mia sceneggiatura :)
TabeaKischka

2

Nel tuo script bash iniziale.

  • tenere traccia del PID del secondo programma

  • prendere il SIGINT

  • quando si rileva un SIGINT, inviare un SIGINT al secondo programma PID


1
Questo potrebbe non aiutare. Se si intercetta SIGINT nello script padre, gli script figlio non saranno in grado di ricevere SIGINT, sia che lo si invii dal padre o da un'altra shell. Ma non è così male come sembra, perché puoi inviare SIGTERM al bambino, che a quanto pare è il segnale preferito da usare, comunque.
PM 2Ring

1
Perché SIGTERM è "preferito"?
MathematicalOrchid,

Sono quasi simili. SIGINT è il segnale inviato dal terminale / utente di controllo, ad es. Ctrl + C. SIGTERM è ciò che puoi anche inviare se vuoi che il processo termini. Altri pensieri qui en.wikipedia.org/wiki/Unix_signal
f01
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.