Controlar qual processo é cancelado por Ctrl + C

13

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

Você deve abortar o segundo programa pressionando Ctrl+ C. O que deve acontecer é que o segundo programa seja interrompido e o script Bash continue executando a limpeza. O que realmente acontece é que o aplicativo principal e o script Bash são finalizados. O que é um problema.

Então eu usei o trapbuilt-in para dizer ao Bash para ignorar o SIGINT. E agora Ctrl+ Cencerra o aplicativo C ++, mas o Bash continua sua execução. Ótimo.

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

Claramente, meu entendimento de como essas coisas funcionam está errado ... Como controle qual processo obtém o SIGINT quando o usuário pressiona Ctrl+ C? Eu quero direcionar esse sinal para apenas um processo específico .

MathematicsOrchid
fonte

Respostas:

11

Depois de muitas e muitas horas pesquisando na Internet, encontrei a resposta.

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

  2. O driver TTY possui uma noção de Foreground Process Group.

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

É por isso que o binário compilado e o script que o lança são derrotados. 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 Grupo de Processos em Primeiro Plano para este TTY. Aparentemente, o comando para fazer isso é

setsid -c <applcation>

E isso é tudo. Agora, quando o usuário pressionar Ctrl+ C, o SIGINT será enviado ao aplicativo (e a qualquer filho que ele tenha) e a mais ninguém. Qual é o que eu queria.

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

  • A adição do -csinalizador torna esse novo grupo de processos o grupo de processos "primeiro plano" do TTY atual. (Ou seja, fica SIGINTquando você pressiona Ctrl+ C)

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

MathematicsOrchid
fonte
5
Você quase conseguiu ... o controle do trabalho está desabilitado por padrão ao executar um script, mas você pode habilitá-lo com set -m. É um pouco mais limpo e simples do que usar setsidtoda vez que você administra uma criança.
Psusi
@psusi Obrigado pela dica! Eu só preciso administrar um filho, então não é grande coisa. Agora eu sei onde procurar no manual do Bash ...
MathematicsOrchid
Ironicamente, tenho o problema inverso em que quero que os pais percebam o sigint, mas isso não ocorre por causa de "truques de tubo inteligentes". Também progredir grupo -> grupo de processos
Andrew Domaszek
2

Como mencionei no comentário a f01, você deve enviar o 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 envia o SIGTERM, as coisas se comportam bem, mas se o traptest envia o SIGINT, o sleeploop nunca o vê.

Se o sleeploop interceptar o SIGTERM e o modo de suspensão estiver em primeiro plano, ele não poderá responder ao sinal até que acorde da suspensão atual. Mas se o modo de suspensão for em segundo plano, ele responderá imediatamente.

PM 2Ring
fonte
Obrigado por esta grande exemplo, ele me ajudou muito na compreensão de como eu posso melhorar o meu script :)
TabeaKischka
2

No script inicial do bash.

  • acompanhar o PID do segundo programa

  • pegar o SIGINT

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

f01
fonte
1
Isso pode não ajudar. Se você interceptar o SIGINT no script pai, os scripts filhos não poderão receber o SIGINT, enviando-o do pai ou de outro shell. Mas isso não é tão ruim quanto parece, porque você pode enviar o SIGTERM para a criança, que aparentemente é o sinal preferido para usar.
PM 2Ring
1
Por que o SIGTERM é "preferido"?
MathematicalOrchid
Eles são quase parecidos. SIGINT é o sinal enviado pelo terminal / usuário de controle, por exemplo, Ctrl + C. SIGTERM é o que você também pode enviar se desejar que o processo seja encerrado. Mais pensamentos aqui en.wikipedia.org/wiki/Unix_signal
f01 4/11/2014