Dado um processo de shell (por exemplo sh
) e seu processo filho (por exemplo cat
), como posso simular o comportamento de Ctrl+ Cusando o ID do processo do shell?
Isto é o que eu tentei:
Em execução sh
e depois cat
:
[user@host ~]$ sh
sh-4.3$ cat
test
test
Enviando SIGINT
para cat
outro terminal:
[user@host ~]$ kill -SIGINT $PID_OF_CAT
cat
recebeu o sinal e terminou (como esperado).
Enviar o sinal para o processo pai parece não funcionar. Por que o sinal não é propagado cat
quando enviado ao processo pai sh
?
Isso não funciona:
[user@host ~]$ kill -SIGINT $PID_OF_SH
Respostas:
Como CTRL+ Cfunciona
A primeira coisa é entender como CTRL+ Cfunciona.
Quando você pressiona CTRL+ C, o emulador de terminal envia um caractere ETX (final de texto / 0x03).
O TTY é configurado de forma que, quando recebe esse caractere, envia um SIGINT ao grupo de processos em primeiro plano do terminal. Essa configuração pode ser visualizada fazendo
stty
e olhandointr = ^C;
. A especificação POSIX diz que quando o INTR é recebido, ele deve enviar um SIGINT ao grupo de processos em primeiro plano desse terminal.Qual é o grupo de processos em primeiro plano?
Então, agora a pergunta é: como você determina qual é o grupo de processos em primeiro plano? O grupo de processos em primeiro plano é simplesmente o grupo de processos que receberá qualquer sinal gerado pelo teclado (SIGTSTOP, SIGINT, etc).
A maneira mais simples de determinar o ID do grupo de processos é usar
ps
:A segunda coluna será o ID do grupo de processos.
Como envio um sinal para o grupo de processos?
Agora que sabemos qual é o ID do grupo de processos, precisamos simular o comportamento do POSIX de enviar um sinal para todo o grupo.
Isso pode ser feito
kill
colocando um-
na frente do ID do grupo.Por exemplo, se o seu ID do grupo de processos for 1234, você usaria:
Simule CTRL+ Cusando o número do terminal.
Portanto, o exposto acima mostra como simular CTRL+ Ccomo um processo manual. Mas e se você souber o número TTY e quiser simular CTRL+ Cpara esse terminal?
Isso se torna muito fácil.
Vamos supor que
$tty
é o terminal que você deseja segmentar (você pode obtê-lo executandotty | sed 's#^/dev/##'
no terminal).Isso enviará um SIGINT para qualquer que seja o grupo de processos em primeiro plano
$tty
.fonte
kill
comando pode falhar.sends a SIGINT to the foreground process group of the terminal.
fork
. Exemplo mínimo de C executável em: unix.stackexchange.com/a/465112/32558Como vinc17 diz, não há razão para isso acontecer. Quando você digita uma sequência de teclas geradora de sinal (por exemplo, Ctrl+ C), o sinal é enviado a todos os processos conectados ao (associados ao) terminal. Não existe esse mecanismo para sinais gerados por
kill
.No entanto, um comando como
enviará o sinal para todos os processos no grupo de processos 12345; veja matar (1) e matar (2) . Os filhos de um shell geralmente estão no grupo de processos do shell (pelo menos, se não forem assíncronos); portanto, o envio do sinal ao negativo do PID do shell pode fazer o que você deseja.
Opa
Como vinc17 aponta, isso não funciona para shells interativos. Aqui está uma alternativa que pode funcionar:
ps -pPID_of_shell
obtém informações do processo no shell.o tpgid=
dizps
para emitir apenas o ID do grupo de processos do terminal, sem cabeçalho. Se for menor que 10000,ps
será exibido com espaços à esquerda; Esse$(echo …)
é um truque rápido para remover os espaços à esquerda (e à direita).Eu fiz isso funcionar em testes superficiais em uma máquina Debian.
fonte
A pergunta contém sua própria resposta. Enviar
SIGINT
para ocat
processo comkill
é uma simulação perfeita do que acontece quando você pressiona^C
.Para ser mais preciso, o caractere de interrupção (
^C
por padrão) enviaSIGINT
para todos os processos no grupo de processos em primeiro plano do terminal. Se em vez decat
você estiver executando um comando mais complicado envolvendo vários processos, você teria que matar o grupo de processos para obter o mesmo efeito que^C
.Quando você executa qualquer comando externo sem o
&
operador em segundo plano, o shell cria um novo grupo de processos para o comando e notifica o terminal de que esse grupo de processos está agora em primeiro plano. O shell ainda está em seu próprio grupo de processos, que não está mais em primeiro plano. Em seguida, o shell aguarda o comando sair.É aí que você parece ter se tornado vítima de um equívoco comum: a idéia de que o shell está fazendo algo para facilitar a interação entre seus processos filhos e o terminal. Isso não é verdade. Depois de concluir o trabalho de configuração (criação do processo, configuração do modo de terminal, criação de pipes e redirecionamento de outros descritores de arquivos e execução do programa de destino), o shell simplesmente espera . O que você digita
cat
não passa pelo shell, seja uma entrada normal ou um caractere especial gerador de sinal^C
. Ocat
processo tem acesso direto ao terminal através de seus próprios descritores de arquivo, e o terminal tem a capacidade de enviar sinais diretamente aocat
processo, porque é o grupo de processos em primeiro plano.Depois que o
cat
processo morre, o shell será notificado, porque é o pai docat
processo. Em seguida, o shell se torna ativo e se coloca em primeiro plano novamente.Aqui está um exercício para aumentar sua compreensão.
No prompt do shell em um novo terminal, execute este comando:
A
exec
palavra-chave faz com que o shell seja executadocat
sem criar um processo filho. O shell é substituído porcat
. O PID que anteriormente pertencia ao shell agora é o PID decat
. Verifique issops
em um terminal diferente. Digite algumas linhas aleatórias e veja que ascat
repete novamente, provando que ainda está se comportando normalmente, apesar de não ter um processo de shell como pai. O que acontecerá quando você pressionar^C
agora?Responda:
fonte
exec cat
pressionar^C
, não caia^C
em um gato. Por que encerraria ocat
que agora substituiu o shell? Desde que o shell foi substituído, o que implementa a lógica de enviar o SIGINT para seus filhos após o recebimento^C
.Não há razão para propagar o
SIGINT
para a criança. Além disso, asystem()
especificação POSIX diz: "A função system () deve ignorar os sinais SIGINT e SIGQUIT e deve bloquear o sinal SIGCHLD enquanto aguarda o término do comando."Se o shell propagasse o recebido
SIGINT
, por exemplo, após um Ctrl-C real, isso significaria que o processo filho receberia oSIGINT
sinal duas vezes, o que pode ter um comportamento indesejado.fonte
system()
. Mas você está certo, se ele capta o sinal (obviamente), não há razão para propagá-lo para baixo.setpgid
Exemplo mínimo do grupo de processos POSIX CPode ser mais fácil entender com um exemplo executável mínimo da API subjacente.
Isso ilustra como o sinal é enviado à criança, se a criança não mudou seu grupo de processos
setpgid
.main.c
GitHub upstream .
Ajuntar com:
Corra sem
setpgid
Sem nenhum argumento da CLI, isso
setpgid
não é feito:Resultado possível:
e o programa trava.
Como podemos ver, o pgid de ambos os processos é o mesmo, à medida que é herdado
fork
.Então, sempre que você clicar em:
Ele produz novamente:
Isso mostra como:
kill(-pgid, SIGINT)
Saia do programa enviando um sinal diferente para os dois processos, por exemplo, SIGQUIT com
Ctrl + \
.Correr com
setpgid
Se você executar com um argumento, por exemplo:
então o filho altera seu pgid e agora apenas uma única assinatura é impressa todas as vezes somente do pai:
E agora, sempre que você clicar em:
somente os pais também recebem o sinal:
Você ainda pode matar o pai como antes com um SIGQUIT:
no entanto, a criança agora tem um PGID diferente e não recebe esse sinal! Isso pode ser visto em:
Você terá que matá-lo explicitamente com:
Isso deixa claro por que existem grupos de sinais: caso contrário, teríamos um monte de processos restantes para serem limpos manualmente o tempo todo.
Testado no Ubuntu 18.04.
fonte