Controla qual processo é cancelado por Ctrl + C

12

Eu tenho um live CD que inicializa no Linux e roda um pequeno script Bash. O script procura e executa um segundo programa (que geralmente é um binário C ++ compilado).

Você deveria ser capaz de abortar o segundo programa pressionando Ctrl + C. O que deve acontecer é que o segundo programa pare e o script Bash continue executando a limpeza. O que realmente acontece é que tanto o aplicativo principal quanto o script Bash terminam. O que é um problema.

Então eu usei o trap builtin para dizer ao Bash para ignorar o SIGINT. E agora Ctrl + C termina o aplicativo C ++, mas o Bash continua sua execução. Ótimo.

Ah sim ... Às vezes o "segundo aplicativo" é outro script do Bash. E em esse caso, o Ctrl + C agora não faz nada .

Claramente, meu entendimento de como essas coisas funcionam está errado ... Como eu controlo qual processo recebe SIGINT quando o usuário pressiona Ctrl + C? Quero direcionar esse sinal para apenas um processo específico .

    
por MathematicalOrchid 22.10.2014 / 11:55

3 respostas

8

Depois de muitas horas de pesquisa na face da Internet, encontrei a resposta.

  1. O Linux tem a noção de um grupo de processos .

  2. O driver TTY possui uma noção do Grupo de processos de primeiro plano.

  3. Quando você pressiona Ctrl + C, o TTY envia SIGINT para todos os processos no grupo de processos em primeiro plano. (Veja também esta entrada de blog .)

É por isso que tanto o binário compilado quanto o script que inicia ambos são corrompidos. Na verdade, eu só quero que o aplicativo principal receba esse sinal, não os scripts de inicialização.

A solução agora é óbvia: precisamos colocar o aplicativo em um novo grupo de processos e torná-lo o primeiro grupo de processos para este TTY. Aparentemente, o comando para fazer isso é

setsid -c <applcation>

E isso é tudo. Agora, quando o usuário pressiona Ctrl + C, SIGINT será enviado para o aplicativo (e qualquer criança que ele possa ter) e mais ninguém. Qual é o que eu queria.

  • setsid por si só coloca o aplicativo em um novo grupo de processos (na verdade, uma nova "sessão" inteira, que aparentemente é um grupo de grupos de processos).

  • A adição do sinal -c faz com que esse novo grupo de processos se torne o grupo de processos "primeiro plano" para o TTY atual. (Ou seja, fica SIGINT quando você pressiona Ctrl + C).

Eu vi muitas informações conflitantes sobre quando o Bash executa ou não os processos em um novo grupo de processos. (Em particular, parece ser diferente para shells "interativos" e "não-interativos".) Eu já vi sugestões de que você talvez consiga fazer isso funcionar com truque de cachimbo inteligente ... não sei. Mas a abordagem acima parece funcionar para mim.

    
por 03.11.2014 / 16:59
2

Como mencionei no comentário ao f01, você deve enviar SIGTERM para o processo filho. Aqui estão alguns scripts que mostram como interceptar ^ C e enviar um sinal para um processo filho.

Primeiro, o pai.

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 agora, a criança.

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 o traptest enviar SIGTERM, as coisas se comportarão bem, mas se o traptest enviar SIGINT, o sleeploop nunca o verá.

Se o sleeploop capturar o SIGTERM e o modo de espera for o primeiro plano, ele não poderá responder ao sinal até que ele seja ativado a partir do modo de suspensão atual. Mas se o modo de suspensão for de fundo, ele responderá imediatamente.

    
por 23.10.2014 / 07:56
2

Em seu script inicial de bash.

  • acompanhe o PID do segundo programa

  • pegue o SIGINT

  • quando você pegou um SIGINT, envie um SIGINT para o segundo programa PID

por 22.10.2014 / 17:21

Tags