Impedindo a propagação do SIGINT para o Processo Pai

10

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

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

Existe uma maneira de substituir esse comportamento padrão? Que apenas o Processo CRIANÇA lida com o SINAL sem propagá-lo para os pais?

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

Guddu
fonte

Respostas:

11

(Inspirado pela resposta de Gilles)

Com o ISIGsinalizador definido, a única maneira de obter o Childscript SIGINTsem que seu pai obtenha SIGINTé estar em seu próprio grupo de processos. Isso pode ser realizado com a set -mopção

Se você ativar a -mopção no Childscript de shell, ele executará o controle do trabalho sem ser interativo. Isso fará com que ele execute coisas em um grupo de processos separado, impedindo que o pai receba SIGINTquando o INTRcaractere for lido.

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

-mEsta opção será suportada se a implementação suportar a opção Utilitários de Portabilidade do Usuário. Todos os trabalhos devem ser executados em seus próprios grupos de processos. Imediatamente antes do shell emitir um prompt após a conclusão do trabalho em segundo plano, uma mensagem relatando o status de saída do trabalho em segundo plano deve ser gravada com erro padrão. Se um trabalho em primeiro plano parar, o shell gravará uma mensagem de erro padrão nesse sentido, formatado conforme descrito pelo utilitário de trabalhos. Além disso, se um trabalho alterar um status diferente de sair (por exemplo, se parar para entrada ou saída ou for interrompido por um sinal SIGSTOP), o shell deve escrever uma mensagem semelhante imediatamente antes de escrever o próximo prompt. Esta opção está ativada por padrão para shells interativos.

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

Exemplo:

  • o Parentscript:

    #!/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 Childscript:

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

É o que acontece quando você pressiona Control+ Cenquanto Childaguarda a 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 SIGINTmanipulador do pai nunca é executado.

Como alternativa, se você preferir modificar em Parentvez de Child, pode fazer o seguinte:

  • o Parentscript:

    #!/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 Childscript (normal; sem necessidade -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 SIGINTpela duração de Child. Isso não aborda sua pergunta, mas pode ser o que você deseja.
  2. Modifique Childpara:
    1. Use stty -gpara fazer backup das configurações atuais do terminal.
    2. Corra stty -isigpara não gerar sinais com os INTR, QUITe SUSPcaracteres.
    3. Em segundo plano, leia a entrada do terminal e envie os sinais conforme apropriado (por exemplo, execute kill -QUIT 0quando Control+ \for lido, kill -INT $$quando Control+ Cfor lido). Isso não é trivial e pode não ser possível fazer com que isso funcione sem problemas, se o Childscript ou qualquer coisa que ele executa for interativo.
    4. Restaure as configurações do terminal antes de sair (idealmente a partir de uma armadilha EXIT).
  3. Igual ao número 2, exceto em vez de executar stty -isig, aguarde o usuário pressionar Enterou alguma outra chave não especial antes de matar Child.
  4. Escreva seu próprio setpgidutilitário em C, Python, Perl, etc., que você pode usar para chamar setpgid(). Aqui está uma implementação grosseira 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
Richard Hansen
fonte
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 matar o pai do programa, organize-o para executar em seu próprio grupo de processos.

Os shells não dão acesso por setpgrpmeio de uma construção embutida ou sintática, mas há uma maneira indireta de alcançá-lo, que é executar o shell interativamente. (Obrigado a Stéphane Gimenez pelo truque.)

ksh -ic '
  … the part that needs to be interruptible without bothering the parent …
'
Gilles 'SO- parar de ser mau'
fonte
+1 para a idéia inteligente, embora tenha cuidado para que o comportamento do shell mude quando é um shell interativo (pelo menos o shell POSIX muda; não estou familiarizado com os detalhes de ksh). Exemplos: ${ENV}é originário, o shell não sai imediatamente quando encontra um erro SIGQUITe SIGTERMé ignorado.
Richard Hansen
1

Bem, na pergunta Stack Overflow que você mencionou, afirma claramente que o pai precisa ser configurado para lidar com o sinal.

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

Então, essas são duas opções. O terceiro seria executar a criança em seu próprio grupo de processos.

bahamat
fonte
Obrigado pela sua resposta. Também preciso descobrir uma maneira de fazer com que o usuário saia do script. Existe uma maneira de definir o ISIG e, ao mesmo tempo, permitir que alguma combinação de teclas CONTRL (CTRL-Q) possa sair do script de shell sem enviar sinais aos pais e da mesma forma? Além disso, você poderia me dizer como eu poderia executar a criança em um grupo de processos diferente do pai?
Guddu