Controla qué proceso se cancela mediante Ctrl + C

13

Tengo un CD en vivo que arranca en Linux y ejecuta un pequeño script Bash. El script busca y ejecuta un segundo programa (que generalmente es un binario compilado de C ++).

Se supone que puedes abortar el segundo programa presionando Ctrl+ C. Lo que debería suceder es que el segundo programa se detiene y el script Bash continúa ejecutando la limpieza. Lo que realmente sucede es que tanto la aplicación principal como el script Bash finalizan. Que es un problema

Así que usé el trapincorporado para decirle a Bash que ignorara SIGINT. Y ahora Ctrl+ Cfinaliza la aplicación C ++, pero Bash continúa su ejecución. Excelente.

Ah, sí ... A veces la "segunda aplicación" es otra secuencia de comandos Bash. Y en ese caso, Ctrl+ Cahora no hace nada en absoluto .

Claramente, mi comprensión de cómo funciona esto está mal ... ¿Cómo controlo qué proceso obtiene SIGINT cuando el usuario presiona Ctrl+ C? Quiero dirigir esta señal a un solo proceso específico .

Orquídea matemática
fuente

Respuestas:

11

Después de muchas, muchas horas de búsqueda en Internet, he encontrado la respuesta.

  1. Linux tiene la noción de un grupo de procesos .

  2. El controlador TTY tiene una noción de Grupo de proceso en primer plano.

  3. Cuando presiona Ctrl+ C, el TTY se envía SIGINTa cada proceso en el Grupo de procesos en primer plano. (Ver también esta entrada de blog ).

Esta es la razón por la que tanto el binario compilado como el script que lo inicia son golpeados. De hecho, solo quiero que la aplicación principal reciba esta señal, no los scripts de inicio.

La solución ahora es obvia: necesitamos colocar la aplicación en un nuevo grupo de procesos y convertirla en el Grupo de procesos en primer plano para este TTY. Al parecer, el comando para hacer eso es

setsid -c <applcation>

Y eso es todo. Ahora, cuando el usuario presiona Ctrl+ C, SIGINT se enviará a la aplicación (y a los hijos que pueda tener) y a nadie más. Que es lo que quería.

  • setsid por sí solo coloca la aplicación en un nuevo grupo de procesos (de hecho, una "sesión" completamente nueva, que aparentemente es un grupo de grupos de procesos).

  • Agregar el -cindicador hace que este nuevo grupo de procesos se convierta en el grupo de procesos "en primer plano" para el TTY actual. (Es decir, se pone SIGINTcuando presionas Ctrl+ C)

He visto mucha información contradictoria sobre cuándo Bash ejecuta o no procesos en un nuevo grupo de procesos. (En particular, parece ser diferente para los proyectiles "interactivos" y "no interactivos".) He visto sugerencias de que tal vez puedas hacer que esto funcione con ingeniosos trucos de tuberías ... No lo sé. Pero el enfoque anterior parece funcionar para mí.

Orquídea matemática
fuente
55
Casi lo tienes ... el control de trabajos está desactivada por defecto cuando se ejecuta un script, pero se puede activar con set -m. Es un poco más limpio y simple que usarlo setsidcada vez que ejecutas a un niño.
psusi
@psusi Gracias por el consejo! Solo necesito ejecutar un niño, así que no es gran cosa. Sin embargo, ahora sé dónde buscar en el manual de Bash ...
MathematicalOrchid
Irónicamente, tengo el problema inverso en el que quiero que el padre atrape la señalización, pero no es así debido a un "truco ingenioso de la tubería". También grupo de progreso -> grupo de proceso
Andrew Domaszek
2

Como mencioné en el comentario a f01, deberías enviar SIGTERM al proceso secundario. Aquí hay un par de scripts que muestran cómo atrapar ^ C y enviar una señal a un proceso secundario.

Primero, el padre.

prueba trampa

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

Y ahora, el niño.

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 envía SIGTERM, las cosas se comportan bien, pero si traptest envía SIGINT, sleeploop nunca lo ve.

Si sleeploop atrapa SIGTERM, y el modo de suspensión está en primer plano, entonces no puede responder a la señal hasta que se despierte de la suspensión actual. Pero si el modo de suspensión es de fondo, responderá de inmediato.

PM 2Ring
fuente
Gracias por este gran ejemplo, me ayudó mucho a comprender cómo puedo mejorar mi script :)
TabeaKischka
2

En tu guión bash de inicio.

  • realizar un seguimiento del PID del segundo programa

  • atrapar la señal

  • cuando haya detectado un SIGINT, envíe un SIGINT al segundo PID del programa

f01
fuente
1
Eso puede no ayudar. Si atrapa SIGINT en el script primario, los scripts secundarios no podrán recibir SIGINT, ya sea que lo envíe desde el primario o desde otro shell. Pero eso no es tan malo como parece, porque de todos modos puede enviar SIGTERM al niño, que aparentemente es la señal preferida.
PM 2Ring
1
¿Por qué se "prefiere" SIGTERM?
MathematicalOrchid
Son casi parecidos. SIGINT es la señal enviada por el terminal / usuario controlador, por ejemplo, Ctrl + C. SIGTERM es lo que también puede enviar si desea que finalice el proceso. Más pensamientos aquí en.wikipedia.org/wiki/Unix_signal
f01