Prevención de la propagación de SIGINT al proceso primario

10

Teniendo en cuenta un escenario en el que un programa principal (podría ser un programa C ++ o un script de shell) ejecuta un script de shell secundario, cuando presionamos Control + C (o cualquier carácter que esté configurado para ser el carácter INTR) mientras se ejecuta el script de shell secundario, se envía un SIGINT a todos los procesos en el grupo de procesos en primer plano. Esto incluye el proceso principal.

Fuente: POSIX.1-2008 XBD sección 11.1.9

¿Hay alguna forma de anular este comportamiento predeterminado? ¿Que el proceso CHILD solo maneja la SEÑAL sin propagarse al padre?

Referencia: Desbordamiento de pila posterior: proceso principal no finaliza cuando se interrumpe el elemento secundario (TRAP INT)

Guddu
fuente

Respuestas:

11

(Inspirado por la respuesta de Gilles)

Con el ISIGindicador establecido, la única forma de que el Childscript se obtenga SIGINTsin que su padre lo obtenga SIGINTes que esté en su propio grupo de procesos. Esto se puede lograr con la set -mopción.

Si activa la -mopción en el Childscript de shell, realizará el control del trabajo sin ser interactivo. Esto hará que ejecute cosas en un grupo de proceso separado, evitando que el padre reciba el SIGINTcuando INTRse lee el personaje.

Aquí está la descripción POSIX de la -mopción :

-mEsta opción se admitirá si la implementación admite la opción Utilidades de portabilidad del usuario. Todos los trabajos se ejecutarán en sus propios grupos de procesos. Inmediatamente antes de que el shell emita un aviso después de completar el trabajo en segundo plano, se escribirá un mensaje de error que informe el estado de salida del trabajo en segundo plano. Si se detiene un trabajo en primer plano, el shell escribirá un mensaje de error estándar a ese efecto, formateado como se describe en la utilidad de trabajos. Además, si un trabajo cambia de estado además de salir (por ejemplo, si se detiene para entrada o salida o es detenido por una señal SIGSTOP), el shell debe escribir un mensaje similar inmediatamente antes de escribir el siguiente mensaje. Esta opción está habilitada de forma predeterminada para shells interactivos.

La -mopción es similar a -i, pero no altera el comportamiento del shell tanto como lo -ihace.

Ejemplo:

  • el Parentguión:

    #!/bin/sh
    
    trap 'echo "PARENT: caught SIGINT; exiting"; exit 1' INT
    
    echo "PARENT: pid=$$"
    echo "PARENT: Spawning child..."
    ./Child
    echo "PARENT: child returned"
    echo "PARENT: exiting normally"
  • el Childguión:

    #!/bin/sh -m
    #         ^^        
    # notice the -m option above!
    
    trap 'echo "CHILD: caught SIGINT; exiting"; exit 1' INT
    
    echo "CHILD: pid=$$"
    echo "CHILD: hit enter to exit"
    read foo
    echo "CHILD: exiting normally"

Esto es lo que sucede cuando presiona Control+ Cmientras Childespera la entrada:

$ ./Parent
PARENT: pid=12233
PARENT: Spawning child...
CHILD: pid=12234
CHILD: hit enter to exit
^CCHILD: caught SIGINT; exiting
PARENT: child returned
PARENT: exiting normally

Observe cómo el controlador del padre SIGINTnunca se ejecuta.

Alternativamente, si prefiere modificar en Parentlugar de Child, puede hacer esto:

  • el Parentguión:

    #!/bin/sh
    
    trap 'echo "PARENT: caught SIGINT; exiting"; exit 1' INT
    
    echo "PARENT: pid=$$"
    echo "PARENT: Spawning child..."
    sh -m ./Child  # or 'sh -m -c ./Child' if Child isn't a shell script
    echo "PARENT: child returned"
    echo "PARENT: exiting normally"
  • el Childscript (normal; no es necesario -m):

    #!/bin/sh
    
    trap 'echo "CHILD: caught SIGINT; exiting"; exit 1' INT
    
    echo "CHILD: pid=$$"
    echo "CHILD: hit enter to exit"
    read foo
    echo "CHILD: exiting normally"

Ideas alternativas

  1. Modifique los otros procesos en el grupo de procesos en primer plano para ignorarlos SIGINTdurante la duración de Child. Esto no responde a su pregunta, pero puede obtener lo que desea.
  2. Modificar Childa:
    1. Use stty -gpara hacer una copia de seguridad de la configuración actual del terminal.
    2. Correr stty -isigpara no generar señales con las INTR, QUITy SUSPcaracteres.
    3. En el fondo, leer la entrada del terminal y enviar las señales a sí mismo como apropiado (por ejemplo, correr kill -QUIT 0cuando Control+ \se lee, kill -INT $$cuando Control+ Cse lee). Esto no es trivial, y puede que no sea posible hacer que funcione sin problemas si el Childscript o cualquier cosa que ejecute esté destinado a ser interactivo.
    4. Restaure la configuración del terminal antes de salir (idealmente desde una trampa activada EXIT).
  3. Igual que el # 2, excepto que en lugar de ejecutarse stty -isig, espere a que el usuario presione Entero alguna otra tecla no especial antes de matar Child.
  4. Escriba su propia setpgidutilidad en C, Python, Perl, etc. que pueda usar para llamar setpgid(). Aquí hay una implementación cruda de C:

    #define _XOPEN_SOURCE 700
    #include <unistd.h>
    #include <signal.h>
    
    int
    main(int argc, char *argv[])
    {
        // todo: add error checking
        void (*backup)(int);
        setpgid(0, 0);
        backup = signal(SIGTTOU, SIG_IGN);
        tcsetpgrp(0, getpid());
        signal(SIGTTOU, backup);
        execvp(argv[1], argv + 1);
        return 1;
    }

    Ejemplo de uso de Child:

    #!/bin/sh
    
    [ "${DID_SETPGID}" = true ] || {
        # restart self after calling setpgid(0, 0)
        exec env DID_SETPGID=true setpgid "$0" "$@"
        # exec failed if control reached this point
        exit 1
    }
    unset DID_SETPGID
    
    # do stuff here
Richard Hansen
fuente
4

Como explica el capítulo que cita de POSIX, SIGINT se envía a todo el grupo de procesos en primer plano. Por lo tanto, para evitar matar al programa principal, organícelo para que se ejecute en su propio grupo de procesos.

Los shells no dan acceso a setpgrptravés de una construcción incorporada o sintáctica, pero hay una forma indirecta de lograrlo, que es ejecutar el shell de forma interactiva. (Gracias a Stéphane Gimenez por el truco).

ksh -ic '
  … the part that needs to be interruptible without bothering the parent …
'
Gilles 'SO- deja de ser malvado'
fuente
+1 para la idea inteligente, aunque tenga en cuenta que el comportamiento del shell cambia cuando es un shell interactivo (al menos el shell POSIX sí; no estoy familiarizado con los detalles de ksh). Ejemplos: ${ENV}es de origen, el shell no se cerrará inmediatamente cuando encuentre un error SIGQUITy SIGTERMse ignorará.
Richard Hansen
1

Bueno, de la pregunta de desbordamiento de pila a la que hizo referencia, establece claramente que el padre debe estar configurado para manejar la señal.

En segundo lugar, la referencia POSIX establece claramente que "si se establece ISIG, el carácter INTR se descartará cuando se procese".

Eso son dos opciones. El tercero sería ejecutar al niño en su propio grupo de procesos.

bahamat
fuente
Gracias por su respuesta. También necesito encontrar una manera de hacer que el usuario salga del script. ¿Hay alguna manera de configurar ISIG y al mismo tiempo permitir que alguna combinación de teclas CONTRL (CTRL-Q puede ser) salga del script de shell sin enviar señales a los padres y de la misma manera? Además, ¿podría decirme cómo podría ejecutar al niño en un grupo de proceso diferente al de su padre?
Guddu