Evitando a propagação de SIGINT para o processo pai

8

Considerando um cenário em que um programa Pai (pode ser um programa C ++ ou um Shell Script) executa um script de shell Filho, quando pressionamos Control + C (ou qualquer caractere configurado como sendo o caracter INTR) enquanto o Script de Shell Filho está sendo executado, um SIGINT é enviado para todos os processos no grupo de processos em primeiro plano. Isso inclui o processo pai.

Fonte: POSIX.1-2008 XBD seção 11.1.9

Existe uma maneira de substituir esse comportamento padrão? Que o processo CHILD sozinho lida com o SINAL sem propagá-lo ao pai?

Referência: Postagem de estouro de pilha - O processo pai não é concluído quando o filho é interrompido (TRAP INT)

    
por Guddu 28.06.2013 / 07:27

3 respostas

8

(Inspirado pela resposta de Gilles)

Com o sinalizador ISIG definido, a única maneira de o script Child obter SIGINT sem que seu pai receba SIGINT é que ele esteja em seu próprio grupo de processos. Isso pode ser feito com a opção set -m .

Se você ativar a opção -m no script de shell Child , ele executará o controle de tarefas sem ser interativo. Isso fará com que ele execute coisas em um grupo de processos separado, impedindo que o pai receba o SIGINT quando o caractere INTR for lido.

Aqui está a descrição POSIX da opção -m :

-m This option shall be supported if the implementation supports the User Portability Utilities option. All jobs shall be run in their own process groups. Immediately before the shell issues a prompt after completion of the background job, a message reporting the exit status of the background job shall be written to standard error. If a foreground job stops, the shell shall write a message to standard error to that effect, formatted as described by the jobs utility. In addition, if a job changes status other than exiting (for example, if it stops for input or output or is stopped by a SIGSTOP signal), the shell shall write a similar message immediately prior to writing the next prompt. This option is enabled by default for interactive shells.

A opção -m é semelhante a -i , mas não altera o comportamento do shell quase tanto quanto -i .

Exemplo:

  • o script Parent :

    #!/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"
    
  • o script Child :

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

Isto é o que acontece quando você aperta Controle + C enquanto Child está esperando pela 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 como o manipulador SIGINT do pai nunca é executado.

Se preferir modificar Parent em vez de Child , você pode fazer isso:

  • o script Parent :

    #!/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"
    
  • o script Child (normal; sem necessidade de -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"
    

Idéias alternativas

  1. Modifique os outros processos no grupo de processos em primeiro plano para ignorar SIGINT pela duração de Child . Isso não resolve sua pergunta, mas pode conseguir o que você quer.
  2. Modifique Child para:
    1. Use stty -g para fazer backup das configurações atuais do terminal.
    2. Execute stty -isig para não gerar sinais com os caracteres INTR , QUIT e SUSP .
    3. Em segundo plano, leia a entrada do terminal e envie os sinais você mesmo conforme apropriado (por exemplo, execute kill -QUIT 0 quando Controle + \ for lido, kill -INT $$ quando Controle + C é lido). Isso não é trivial, e pode não ser possível fazer isso funcionar sem problemas se o script Child ou qualquer coisa que ele executa for feito para ser interativo.
    4. Restaure as configurações do terminal antes de sair (de preferência de uma interceptação em EXIT ).
  3. O mesmo que # 2, exceto que, em vez de executar stty -isig , espere o usuário pressionar Enter ou alguma outra chave não especial antes de matar Child .
  4. Escreva seu próprio utilitário setpgid em C, Python, Perl, etc., que você pode usar para chamar setpgid() . Aqui está uma implementação bruta 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;
    }
    

    Exemplo 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
    
por 30.06.2013 / 01:32
4

Como o capítulo que você cita do POSIX explica, o SIGINT é enviado para todo o grupo de processos em primeiro plano. Portanto, para evitar a eliminação do pai do programa, faça com que ele seja executado em seu próprio grupo de processos.

Os shells não dão acesso ao setpgrp através de uma construção integrada ou sintática, mas existe uma maneira indireta de alcançá-lo, que é executar o shell de forma interativa. (Obrigado a Stéphane Gimenez pelo truque.)

ksh -ic '
  … the part that needs to be interruptible without bothering the parent …
'
    
por 29.06.2013 / 02:17
1

Bem, na pergunta sobre estouro de pilha que você mencionou, ele afirma claramente que o pai precisa ser configurado para lidar com o sinal.

Em segundo lugar, a referência POSIX afirma claramente que "Se o ISIG for definido, o caractere INTR será descartado quando processado".

Então são duas opções. A terceira seria executar a criança em seu próprio grupo de processos.

    
por 28.06.2013 / 07:35